mirror of
https://github.com/openharmony/third_party_iowow.git
synced 2026-07-01 14:23:51 -04:00
update from v1.4.15 to v1.4.16
Signed-off-by: zhouhaifeng <kutcher.zhou@huawei.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
cd ./build/src/utils/tests
|
||||
cd ./build/src/fs/tests
|
||||
file ./iwfs_test2
|
||||
#set args -c
|
||||
|
||||
set confirm off
|
||||
set follow-fork-mode parent
|
||||
set detach-on-fork on
|
||||
set print elements 4096
|
||||
|
||||
@@ -17,10 +17,12 @@ config("iowow_public_config") {
|
||||
include_dirs = [
|
||||
"//third_party/iowow/src",
|
||||
"//third_party/iowow/src/fs",
|
||||
"//third_party/iowow/src/json",
|
||||
"//third_party/iowow/src/kv",
|
||||
"//third_party/iowow/src/log",
|
||||
"//third_party/iowow/src/platform",
|
||||
"//third_party/iowow/src/rdb",
|
||||
"//third_party/iowow/src/re",
|
||||
"//third_party/iowow/src/utils",
|
||||
]
|
||||
}
|
||||
@@ -29,10 +31,12 @@ config("iowow_config") {
|
||||
include_dirs = [
|
||||
"//third_party/iowow/src",
|
||||
"//third_party/iowow/src/fs",
|
||||
"//third_party/iowow/src/json",
|
||||
"//third_party/iowow/src/kv",
|
||||
"//third_party/iowow/src/log",
|
||||
"//third_party/iowow/src/platform",
|
||||
"//third_party/iowow/src/rdb",
|
||||
"//third_party/iowow/src/re",
|
||||
"//third_party/iowow/src/utils",
|
||||
]
|
||||
|
||||
@@ -90,19 +94,25 @@ ohos_shared_library("iowow") {
|
||||
"src/fs/iwfs.c",
|
||||
"src/fs/iwfsmfile.c",
|
||||
"src/iowow.c",
|
||||
"src/json/iwbinn.c",
|
||||
"src/json/iwjser.c",
|
||||
"src/json/iwjson.c",
|
||||
"src/kv/iwal.c",
|
||||
"src/kv/iwkv.c",
|
||||
"src/log/iwlog.c",
|
||||
"src/platform/iwp.c",
|
||||
"src/rdb/iwrdb.c",
|
||||
"src/re/compile.c",
|
||||
"src/re/iwre.c",
|
||||
"src/re/parse.c",
|
||||
"src/re/vm.c",
|
||||
"src/utils/iwarr.c",
|
||||
"src/utils/iwavl.c",
|
||||
"src/utils/iwconv.c",
|
||||
"src/utils/iwhmap.c",
|
||||
"src/utils/iwini.c",
|
||||
"src/utils/iwpool.c",
|
||||
"src/utils/iwrb.c",
|
||||
"src/utils/iwre.c",
|
||||
"src/utils/iwsha2.c",
|
||||
"src/utils/iwstree.c",
|
||||
"src/utils/iwstw.c",
|
||||
"src/utils/iwth.c",
|
||||
"src/utils/iwtp.c",
|
||||
@@ -111,6 +121,7 @@ ohos_shared_library("iowow") {
|
||||
"src/utils/iwxstr.c",
|
||||
"src/utils/mt19937ar.c",
|
||||
"src/utils/murmur3.c",
|
||||
"src/utils/utf8proc.c",
|
||||
]
|
||||
|
||||
part_name = "iowow"
|
||||
|
||||
@@ -35,6 +35,7 @@ set(${PROJECT_NAME}_VERSION_PATCH ${PROJECT_VERSION_PATCH})
|
||||
option(BUILD_SHARED_LIBS "Build shared libraries" ON)
|
||||
option(BUILD_TESTS "Build test cases" OFF)
|
||||
option(ASAN "Turn on address sanitizer" OFF)
|
||||
option(UBSAN "Turn on UB sanitizer" OFF)
|
||||
option(BUILD_EXAMPLES "Build example projects" ON)
|
||||
option(BUILD_BENCHMARKS "Build benchmarks" OFF)
|
||||
option(PACKAGE_DEB "Build .deb instalation packages" OFF)
|
||||
|
||||
@@ -1,3 +1,37 @@
|
||||
iowow (1.4.16) testing; urgency=medium
|
||||
|
||||
* Fixed behavior of iwp_tmpdir() accourding to #47
|
||||
* Fixed Incorrect serialization of INT64_MIN by iwitoa() #48 (iwconv.h)
|
||||
* Added iwxstr_insert() (iwxstr.h)
|
||||
* ~8% IO performance improvements due to use of MADV_RANDOM
|
||||
* Fixed Github Vulnerability Report GHSL-2022-066
|
||||
* Added additional IW_ERROR_XX codes (iwlog.h)
|
||||
* Added VERBOSE log level (iwlog.h)
|
||||
* Added iwhmap_put_str() (iwhmap.h)
|
||||
* Added iwulist_remove_first_by(), iwulist_find_first() (iwarr.h)
|
||||
* Added iwxstr_new_printf() (iwxstr.h)
|
||||
* Reduced iwkv code complexity. Removed in-memory db cache since benchmarks shows only minor perf imprivements with cache.
|
||||
* Fixed many of UB errors (eg: misaligned access)
|
||||
* Removed dependency on kbtree.h replaced with iwavl.h
|
||||
* Added json module (migrated from ejdb) (iwjson.h, iwbinn.h)
|
||||
* Added platform neutral iwp_basename() and iwp_dirname() (iwp.h)
|
||||
* Added iwu_file_read_as_buf_len() (iwutils.h)
|
||||
* Added IW_NORET (basedefs.h)
|
||||
* iwxstr_destroy_keep_ptr() now returns pointer to underlying buffer
|
||||
* IWHMAP can operate in LRU cache mode (iwhmap.h)
|
||||
* Added .ini file parsing utility module (iwini.h)
|
||||
* Added iw_cond_timed_wait_ms() (iwth.h)
|
||||
* Added iwstw_set_on_task_discard(), iwstw_schedule_only() (iwstw.h)
|
||||
* Fixed iwp_exec_path() on FreeBSD sinceprocfs(5) is not mounted by default in FreeBSD.
|
||||
* iwp_exec_path() implemented for FreeBSD & Macos
|
||||
* Added `int64_t iwatoi2(const char *str, size_t len)` (iwconv.h)
|
||||
* iwpool_split_xx() now returns const pointer (iwpool.h)
|
||||
* Fixed iwre() regexp compilation error.
|
||||
* Added new regexp API implementation (iwre.h) based on https://github.com/jserv/cregex/
|
||||
* Removed iwsha256() from sources. Use iwnet/beassl for hashing instead.
|
||||
|
||||
-- Anton Adamansky <adamansky@gmail.com> Mon, 14 Nov 2022 18:31:50 +0200
|
||||
|
||||
iowow (1.4.15) testing; urgency=medium
|
||||
|
||||
* Added format checking __attribute__ to all printf like functions.
|
||||
|
||||
@@ -67,6 +67,8 @@ Note:If the text contains special characters, please escape them according to th
|
||||
<filteritem type="filepath" name="release.sh" desc="the header of the file is not license" />
|
||||
<filteritem type="filepath" name="src/bindings/ejdb2_flutter/ios/ejdb2_flutter.podspec" desc="the header of the file is not license" />
|
||||
<filteritem type="filepath" name="src/utils/iwre.c" desc="the header of the file is not license" />
|
||||
<filteritem type="filepath" name="src/utils/iwavl.h" desc="CC0 1.0 Universal" />
|
||||
<filteritem type="filepath" name="src/utils/iwavl.c" desc="CC0 1.0 Universal" />
|
||||
</filefilter>
|
||||
<filefilter name="copyrightPolicyFilter" desc="Filters for copyright header policies">
|
||||
</filefilter>
|
||||
@@ -204,6 +206,23 @@ Note:If the text contains special characters, please escape them according to th
|
||||
THE SOFTWARE IS PROVIDED >AS IS>. USE ENTIRELY AT YOUR OWN RISK."
|
||||
desc=""/>
|
||||
</licensematcher>
|
||||
<licensematcher name="CC0 1.0 Universal" desc="">
|
||||
<licensetext name="
|
||||
Written in 2014-2016 by Eric Biggers >ebiggers3@gmail.com>
|
||||
|
||||
To the extent possible under law, the author(s) have dedicated all copyright
|
||||
and related and neighboring rights to this software to the public domain
|
||||
worldwide via the Creative Commons Zero 1.0 Universal Public Domain
|
||||
Dedication (the >CC0>).
|
||||
|
||||
This software is distributed in the hope that it will be useful, but WITHOUT
|
||||
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
FOR A PARTICULAR PURPOSE. See the CC0 for more details.
|
||||
|
||||
You should have received a copy of the CC0 along with this software; if not
|
||||
see >http://creativecommons.org/publicdomain/zero/1.0/>."
|
||||
desc=""/>
|
||||
</licensematcher>
|
||||
</licensematcherlist>
|
||||
</oatconfig>
|
||||
</configuration>
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
"Name": "IOWOW",
|
||||
"License": "MIT License",
|
||||
"License File": "LICENSE",
|
||||
"Version Number": "v1.4.15",
|
||||
"Version Number": "v1.4.16",
|
||||
"Owner": "kutcher.zhou@huawei.com",
|
||||
"Upstream URL": "https://github.com/Softmotions/iowow",
|
||||
"Description": "The skiplist based persistent key/value storage engine"
|
||||
|
||||
@@ -1,20 +1,25 @@
|
||||
IOWOW - The C11 persistent key/value database engine based on [skip list](https://en.wikipedia.org/wiki/Skip_list)
|
||||
IOWOW — `C11` utility library and persistent key/value storage engine
|
||||
==================================================================================================================
|
||||
|
||||
[](https://t.me/ejdb2)
|
||||
[](https://github.com/Softmotions/iowow/blob/master/LICENSE)
|
||||

|
||||
|
||||
Website http://iowow.io
|
||||
Website https://iowow.softmotions.com
|
||||
|
||||
# Key components
|
||||
|
||||
* [iwkv.h](https://github.com/Softmotions/iowow/blob/master/src/kv/iwkv.h) Persistent key/value database engine
|
||||
* [iwfsmfile.h](https://github.com/Softmotions/iowow/blob/master/src/fs/iwfsmfile.h) File blocks allocation manager like `malloc()` on files
|
||||
* [utils](https://github.com/Softmotions/iowow/tree/master/src/utils) Useful data structures and various commonly used routines.
|
||||
* [json](https://github.com/Softmotions/iowow/tree/master/src/json) JSON parsing and manipulation routines incluing JSON Patch/Pointers support.
|
||||
* [iwkv.h](https://github.com/Softmotions/iowow/blob/master/src/kv/iwkv.h) Persistent key/value database engine.
|
||||
* [iwfsmfile.h](https://github.com/Softmotions/iowow/blob/master/src/fs/iwfsmfile.h) File blocks allocation manager like `malloc()` on files.
|
||||
|
||||
# IWKV
|
||||
## Used by
|
||||
|
||||
## Features
|
||||
* EJDB2 — Embeddable JSON database engine. http://ejdb.org
|
||||
* [Wirow video conferencing platform](https://github.com/wirow-io/wirow-server/)
|
||||
* [Pure C Asynchronous HTTP/IO library with websockets, SSL, routing. ](https://github.com/Softmotions/iwnet)
|
||||
|
||||
## IWKV Features
|
||||
|
||||
* Support of multiple key-value databases within a single file
|
||||
* Online database backups
|
||||
@@ -25,30 +30,16 @@ Website http://iowow.io
|
||||
* Good performance comparing its main competitors: `lmdb`, `leveldb`, `kyoto cabinet`
|
||||
* Tiny C11 library (200Kb) can be easily embedded into any software
|
||||
|
||||
[](https://iowow.io/articles/intro/)
|
||||
[](https://iowow.softmotions.com/articles/intro/)
|
||||
|
||||
|
||||
## Used by
|
||||
|
||||
* EJDB - Embeddable JSON database engine. http://ejdb.org
|
||||
|
||||
## Limitations
|
||||
## IWKV Limitations
|
||||
|
||||
* Maximum iwkv storage file size: `512 GB (0x7fffffff80)`
|
||||
* Total size of a single key+value record must be not greater than `255Mb (0xfffffff)`
|
||||
* In-memory cache for every opened database takes `~130Kb`, cache can be disposed by `iwkv_db_cache_release()`
|
||||
|
||||
# Supported platforms
|
||||
|
||||
## Linux
|
||||
### Ubuntu/Debian
|
||||
#### PPA repository
|
||||
|
||||
```sh
|
||||
sudo add-apt-repository ppa:adamansky/iwowow
|
||||
sudo apt-get update
|
||||
sudo apt-get install iowow
|
||||
```
|
||||
|
||||
#### Building debian packages
|
||||
|
||||
@@ -339,4 +330,9 @@ Arsenal: 40
|
||||
AFC Bournemouth: 27
|
||||
```
|
||||
|
||||
## IWSTART
|
||||
|
||||
IWSTART is an automatic CMake initial project generator for C projects based on iowow / [iwnet](https://github.com/Softmotions/iwnet) / [ejdb2](https://github.com/Softmotions/ejdb) libs.
|
||||
|
||||
https://github.com/Softmotions/iwstart
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ if (NOT WINTOOLS_WGET_EXEC)
|
||||
endif()
|
||||
|
||||
set(WINTOOLS_DIR ${CMAKE_BINARY_DIR}/WINTOOLS)
|
||||
set(WINTOOLS_DL_ROOT "http://softmotions.com/windev")
|
||||
set(WINTOOLS_DL_ROOT "https://assets.softmotions.com/windev")
|
||||
|
||||
if (NOT EXISTS ${WINTOOLS_DIR})
|
||||
file(MAKE_DIRECTORY ${WINTOOLS_DIR})
|
||||
|
||||
+14
-11
@@ -4,7 +4,7 @@ if (NOT CMAKE_BUILD_TYPE)
|
||||
message(FATAL_ERROR "Please specify the build type -DCMAKE_BUILD_TYPE=Debug|Release|RelWithDebInfo")
|
||||
endif()
|
||||
|
||||
set(MODULES log utils platform fs rdb kv)
|
||||
set(MODULES log utils platform fs rdb re json kv)
|
||||
|
||||
set(PROJECT_LLIBRARIES)
|
||||
set(PROJECT_INCLUDE_DIRS)
|
||||
@@ -111,11 +111,6 @@ if (HAVE_CLOCK_MONOTONIC)
|
||||
add_definitions(-DIW_HAVE_CLOCK_MONOTONIC)
|
||||
endif()
|
||||
|
||||
check_symbol_exists(basename_r libgen.h HAVE_BASENAME_R)
|
||||
if (HAVE_BASENAME_R)
|
||||
set_source_files_properties(log/iwlog.c PROPERTIES COMPILE_FLAGS -DIW_HAVE_BASENAME_R)
|
||||
endif()
|
||||
|
||||
foreach(HF IN ITEMS stdlib stddef stdint stdbool stdatomic unistd dirent)
|
||||
string(TOUPPER "${HF}" UHF)
|
||||
check_include_file(${HF}.h "IW_HAVE_${UHF}")
|
||||
@@ -161,8 +156,6 @@ list(APPEND PUB_HDRS ${CMAKE_CURRENT_SOURCE_DIR}/basedefs.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/utils/iwconv.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/utils/iwhmap.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/utils/iwpool.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/utils/iwsha2.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/utils/iwstree.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/utils/iwstw.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/utils/iwth.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/utils/iwutils.h
|
||||
@@ -171,7 +164,13 @@ list(APPEND PUB_HDRS ${CMAKE_CURRENT_SOURCE_DIR}/basedefs.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/utils/murmur3.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/utils/iwtp.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/utils/iwrb.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/utils/iwre.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/utils/iwini.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/utils/utf8proc.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/utils/iwavl.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/re/iwre.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/json/iwjson.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/json/iwjson_internal.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/json/iwbinn.h
|
||||
)
|
||||
|
||||
list(REMOVE_DUPLICATES PROJECT_LLIBRARIES)
|
||||
@@ -181,8 +180,9 @@ include_directories(${PROJECT_INCLUDE_DIRS})
|
||||
# -pg -no-pie
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} \
|
||||
-std=gnu11 -fsigned-char -pedantic \
|
||||
-Wfatal-errors -Wall -Wextra -Wno-sign-compare -Wno-unused-parameter \
|
||||
-Wno-unknown-pragmas -Wno-unused-function -Wno-missing-field-initializers \
|
||||
-Wfatal-errors -Wall -Wextra \
|
||||
-Wno-sign-compare -Wno-unused-parameter \
|
||||
-Wno-implicit-fallthrough -Wno-unknown-pragmas -Wno-unused-function -Wno-missing-field-initializers \
|
||||
-Wno-missing-braces")
|
||||
if (APPLE)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-shorten-64-to-32")
|
||||
@@ -199,6 +199,8 @@ endif()
|
||||
|
||||
if (ASAN)
|
||||
set(CMAKE_C_ASAN "-fsanitize=address -fno-omit-frame-pointer")
|
||||
elseif (UBSAN)
|
||||
set(CMAKE_C_ASAN "-fsanitize=undefined -fno-omit-frame-pointer")
|
||||
endif()
|
||||
|
||||
set(CMAKE_C_FLAGS_DEBUG "-O0 -g -ggdb -Werror -DDEBUG -D_DEBUG -UNDEBUG \
|
||||
@@ -351,6 +353,7 @@ message("\n${PROJECT_NAME} PUB_HDRS: ${PUB_HDRS}")
|
||||
message("\n${PROJECT_NAME} CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}")
|
||||
message("${PROJECT_NAME} BUILD_SHARED_LIBS: ${BUILD_SHARED_LIBS}")
|
||||
message("${PROJECT_NAME} BUILD_TESTS: ${BUILD_TESTS}")
|
||||
message("${PROJECT_NAME} CMAKE_C_ASAN: ${CMAKE_C_ASAN}")
|
||||
message("${PROJECT_NAME} BUILD_EXAMPLES: ${BUILD_EXAMPLES}")
|
||||
message("${PROJECT_NAME} BUILD_BENCHMARKS: ${BUILD_BENCHMARKS}")
|
||||
message("${PROJECT_NAME} BUILD_DOCUMENTATION: ${BUILD_DOCUMENTATION}")
|
||||
|
||||
+34
-10
@@ -44,9 +44,9 @@
|
||||
#define IW_XSTR(s) IW_STR(s)
|
||||
#define IW_STR(s) #s
|
||||
|
||||
#define IW_MAX(X__, Y__) ({ __typeof__(X__) x = (X__); __typeof__(Y__) y = (Y__); x < y ? y : x; })
|
||||
#define IW_MIN(X__, Y__) ({ __typeof__(X__) x = (X__); __typeof__(Y__) y = (Y__); x < y ? x : y; })
|
||||
#define IW_LLEN(L__) (sizeof(L__) - 1)
|
||||
#define IW_MAX(x__, y__) ({ __typeof__(x__) x = (x__); __typeof__(y__) y = (y__); x < y ? y : x; })
|
||||
#define IW_MIN(x__, y__) ({ __typeof__(x__) x = (x__); __typeof__(y__) y = (y__); x < y ? x : y; })
|
||||
#define IW_LLEN(l__) (sizeof(l__) - 1)
|
||||
|
||||
#if (defined(_WIN32) || defined(_WIN64))
|
||||
#if (defined(IW_NODLL) || defined(IW_STATIC))
|
||||
@@ -75,28 +75,54 @@
|
||||
#define IW_SOFT_INLINE static inline
|
||||
|
||||
#if __GNUC__ >= 4
|
||||
#define WUR __attribute__((__warn_unused_result__))
|
||||
#define WUR __attribute__((warn_unused_result))
|
||||
#define IW_ALLOC __attribute__((malloc)) __attribute__((warn_unused_result))
|
||||
#define IW_NORET __attribute__((noreturn))
|
||||
#else
|
||||
#define WUR
|
||||
#define IW_ALLOC
|
||||
#define IW_NORET
|
||||
#endif
|
||||
|
||||
#define IW_CONSTRUCTOR __attribute__((constructor))
|
||||
#define IW_DESTRUCTOR __attribute__((destructor))
|
||||
|
||||
#define IW_CLEANUP(func__) __attribute__(cleanup(func__))
|
||||
|
||||
#define IW_CLEANUP_FUNC(type__, func__) \
|
||||
static inline void func__ ## _cc(type__ * p) { \
|
||||
if (*p) { \
|
||||
*p = func__(*p); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define IW_CLEANUP_DESTROY_FUNC(type__, func__) \
|
||||
static inline void func__ ## _cc(type__ * p) { \
|
||||
if (*p) { \
|
||||
func__(*p); \
|
||||
} \
|
||||
}
|
||||
|
||||
|
||||
#define IW_SENTINEL __attribute__((sentinel))
|
||||
|
||||
#define IW_ARR_STATIC static
|
||||
#define IW_ARR_CONST const
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#define INVALIDHANDLE(_HNDL) \
|
||||
(((_HNDL) == INVALID_HANDLE_VALUE) || (_HNDL) == NULL)
|
||||
#define INVALIDHANDLE(h__) \
|
||||
(((h__) == INVALID_HANDLE_VALUE) || (h__) == NULL)
|
||||
#else
|
||||
typedef int HANDLE;
|
||||
#define INVALID_HANDLE_VALUE (-1)
|
||||
#define INVALIDHANDLE(_HNDL) ((_HNDL) < 0 || (_HNDL) == UINT16_MAX)
|
||||
#define INVALIDHANDLE(h__) ((h__) < 0 || (h__) == UINT16_MAX)
|
||||
#endif
|
||||
|
||||
#define IW_ERROR_START 70000
|
||||
|
||||
#define IWNUMBUF_SIZE 32
|
||||
|
||||
#ifdef _WIN32
|
||||
#define IW_PATH_CHR '\\'
|
||||
#define IW_PATH_STR "\\"
|
||||
@@ -213,9 +239,7 @@ typedef int HANDLE;
|
||||
#endif
|
||||
|
||||
#if defined(NDEBUG)
|
||||
#define IW_DODEBUG(IW_expr_) \
|
||||
do { \
|
||||
} while (0)
|
||||
#define IW_DODEBUG(IW_expr_)
|
||||
#else
|
||||
#define IW_DODEBUG(IW_expr_) \
|
||||
{ IW_expr_; }
|
||||
|
||||
+14
-2
@@ -172,9 +172,21 @@ static iwrc _exfile_initmmap_slot_lw(struct IWFS_EXT *f, MMAPSLOT *s) {
|
||||
iwlog_ecode_error3(rc);
|
||||
return rc;
|
||||
}
|
||||
#ifdef MADV_DONTFORK
|
||||
madvise(s->mmap, s->len, MADV_DONTFORK);
|
||||
|
||||
flags = 0;
|
||||
#ifdef MADV_RANDOM
|
||||
if (s->mmopts & IWFS_MMAP_RANDOM) {
|
||||
flags |= MADV_RANDOM;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef MADV_DONTFORK
|
||||
flags |= MADV_DONTFORK;
|
||||
#endif
|
||||
|
||||
if (flags) {
|
||||
madvise(s->mmap, s->len, flags);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -87,6 +87,8 @@ typedef uint8_t iwfs_ext_mmap_opts_t;
|
||||
#define IWFS_MMAP_SHARED ((iwfs_ext_mmap_opts_t) 0x00U)
|
||||
/** Use private mmap */
|
||||
#define IWFS_MMAP_PRIVATE ((iwfs_ext_mmap_opts_t) 0x01U)
|
||||
/** Use mmap in random access pattern */
|
||||
#define IWFS_MMAP_RANDOM ((iwfs_ext_mmap_opts_t) 0x02U)
|
||||
|
||||
/**
|
||||
* @brief File resize policy function type.
|
||||
|
||||
+541
-575
File diff suppressed because it is too large
Load Diff
+3
-4
@@ -175,10 +175,9 @@ typedef struct IWFS_FSM_STATE {
|
||||
IWFS_EXT_STATE exfile; /**< File pool state */
|
||||
size_t block_size; /**< Size of data block in bytes. */
|
||||
iwfs_fsm_openflags oflags; /**< Operation mode flags. */
|
||||
uint32_t hdrlen; /**< Length of custom file header length in bytes */
|
||||
uint64_t blocks_num; /**< Number of available data blocks. */
|
||||
uint64_t free_segments_num; /**< Number of free (deallocated) continuous data
|
||||
segments. */
|
||||
uint32_t free_segments_num; /**< Number of free (deallocated) continuous data segments */
|
||||
uint32_t hdrlen; /**< Length of custom file header length in bytes */
|
||||
double_t avg_alloc_size; /**< Average allocation number of blocks */
|
||||
double_t alloc_dispersion; /**< Average allocation blocks dispersion */
|
||||
} IWFS_FSM_STATE;
|
||||
@@ -197,7 +196,7 @@ typedef struct IWFS_FSMDBG_STATE {
|
||||
* and free space blocks management using bitmaps.
|
||||
*/
|
||||
typedef struct IWFS_FSM {
|
||||
struct IWFS_FSM_IMPL *impl;
|
||||
struct fsm *impl;
|
||||
|
||||
/**
|
||||
* @brief Allocate a continuous address space within a file
|
||||
|
||||
@@ -326,9 +326,12 @@ void test_fsm_uniform_alloc_impl(int mmap_all) {
|
||||
CU_ASSERT_EQUAL(state1.state.alloc_dispersion, 0);
|
||||
}
|
||||
|
||||
|
||||
rc = fsm.close(&fsm);
|
||||
CU_ASSERT_FALSE_FATAL(rc);
|
||||
|
||||
return;
|
||||
|
||||
opts.exfile.file.omode = IWFS_OREAD;
|
||||
rc = iwfs_fsmfile_open(&fsm, &opts);
|
||||
CU_ASSERT_FALSE_FATAL(rc);
|
||||
@@ -413,7 +416,7 @@ typedef struct {
|
||||
|
||||
//!!!! TODO this test is not good for multithreaded env, refactoring needed
|
||||
|
||||
static void *recordsthr(void *op) {
|
||||
static void* recordsthr(void *op) {
|
||||
FSMRECTASK *task = op;
|
||||
iwrc rc;
|
||||
FSMREC *rec, *tmp;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "utils/kbtree.h"
|
||||
#include "kbtree.h"
|
||||
|
||||
#define NRECS 10000
|
||||
#define RECSZ (10 * 1024)
|
||||
|
||||
+9
-16
@@ -37,6 +37,7 @@ static_assert(sizeof(off_t) == 8, "sizeof(off_t) == 8 bytes");
|
||||
|
||||
iwrc iwfs_init(void);
|
||||
iwrc iwkv_init(void);
|
||||
iwrc jbl_init(void);
|
||||
|
||||
iwrc iw_init(void) {
|
||||
iwrc rc;
|
||||
@@ -44,33 +45,25 @@ iwrc iw_init(void) {
|
||||
if (!__sync_bool_compare_and_swap(&_iw_initialized, 0, 1)) {
|
||||
return 0; // initialized already
|
||||
}
|
||||
rc = iwlog_init();
|
||||
RCGO(rc, finish);
|
||||
|
||||
rc = iwu_init();
|
||||
RCGO(rc, finish);
|
||||
|
||||
rc = iwp_init();
|
||||
RCGO(rc, finish);
|
||||
RCC(rc, finish, iwlog_init());
|
||||
RCC(rc, finish, iwu_init());
|
||||
RCC(rc, finish, iwp_init());
|
||||
RCC(rc, finish, jbl_init());
|
||||
|
||||
uint64_t ts;
|
||||
rc = iwp_current_time_ms(&ts, false);
|
||||
RCRET(rc);
|
||||
RCC(rc, finish, iwp_current_time_ms(&ts, false));
|
||||
ts = IW_SWAB64(ts);
|
||||
ts >>= 32;
|
||||
iwu_rand_seed(ts);
|
||||
|
||||
rc = iwfs_init();
|
||||
RCGO(rc, finish);
|
||||
|
||||
rc = iwkv_init();
|
||||
RCGO(rc, finish);
|
||||
RCC(rc, finish, iwfs_init());
|
||||
RCC(rc, finish, iwkv_init());
|
||||
|
||||
finish:
|
||||
return rc;
|
||||
}
|
||||
|
||||
const char *iowow_version_full(void) {
|
||||
const char* iowow_version_full(void) {
|
||||
return IOWOW_VERSION;
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -56,10 +56,10 @@
|
||||
#error Unknown CPU bits
|
||||
#endif
|
||||
|
||||
#define IOWOW_VERSION "1.4.15"
|
||||
#define IOWOW_VERSION "1.4.16"
|
||||
#define IOWOW_VERSION_MAJOR 1
|
||||
#define IOWOW_VERSION_MINOR 4
|
||||
#define IOWOW_VERSION_PATCH 15
|
||||
#define IOWOW_VERSION_PATCH 16
|
||||
|
||||
#ifndef static_assert
|
||||
#define static_assert _Static_assert
|
||||
|
||||
+3153
File diff suppressed because it is too large
Load Diff
+1053
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,597 @@
|
||||
#include "iwjson.h"
|
||||
#include "iwjson_internal.h"
|
||||
#include "iwconv.h"
|
||||
#include "utf8proc.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <locale.h>
|
||||
#include <assert.h>
|
||||
|
||||
#define IS_WHITESPACE(c_) ((unsigned char) (c_) <= (unsigned char) ' ')
|
||||
|
||||
/** JSON parsing context */
|
||||
typedef struct JCTX {
|
||||
IWPOOL *pool;
|
||||
JBL_NODE root;
|
||||
const char *buf;
|
||||
const char *sp;
|
||||
iwrc rc;
|
||||
} JCTX;
|
||||
|
||||
static void _jbn_add_item(JBL_NODE parent, JBL_NODE node) {
|
||||
assert(parent && node);
|
||||
node->next = 0;
|
||||
node->parent = parent;
|
||||
if (parent->child) {
|
||||
JBL_NODE prev = parent->child->prev;
|
||||
parent->child->prev = node;
|
||||
if (prev) { // -V1051
|
||||
prev->next = node;
|
||||
node->prev = prev;
|
||||
} else {
|
||||
parent->child->next = node;
|
||||
node->prev = parent->child;
|
||||
}
|
||||
} else {
|
||||
parent->child = node;
|
||||
}
|
||||
if (parent->type == JBV_ARRAY) {
|
||||
if (node->prev) {
|
||||
node->klidx = node->prev->klidx + 1;
|
||||
} else {
|
||||
node->klidx = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static JBL_NODE _jbl_json_create_node(jbl_type_t type, const char *key, int klidx, JBL_NODE parent, JCTX *ctx) {
|
||||
JBL_NODE node = iwpool_calloc(sizeof(*node), ctx->pool);
|
||||
if (!node) {
|
||||
ctx->rc = iwrc_set_errno(IW_ERROR_ALLOC, errno);
|
||||
return 0;
|
||||
}
|
||||
node->type = type;
|
||||
node->key = key;
|
||||
node->klidx = klidx;
|
||||
if (parent) {
|
||||
_jbn_add_item(parent, node);
|
||||
}
|
||||
if (!ctx->root) {
|
||||
ctx->root = node;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
IW_INLINE void _jbl_skip_bom(JCTX *ctx) {
|
||||
const char *p = ctx->buf;
|
||||
if ((p[0] == '\xEF') && (p[1] == '\xBB') && (p[2] == '\xBF')) {
|
||||
ctx->buf += 3;
|
||||
}
|
||||
}
|
||||
|
||||
IW_INLINE int _jbl_hex(char c) {
|
||||
if ((c >= '0') && (c <= '9')) {
|
||||
return c - '0';
|
||||
}
|
||||
if ((c >= 'a') && (c <= 'f')) {
|
||||
return c - 'a' + 10;
|
||||
}
|
||||
if ((c >= 'A') && (c <= 'F')) {
|
||||
return c - 'A' + 10;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int _jbl_unescape_json_string(const char *p, char *d, int dlen, const char **end, iwrc *rcp) {
|
||||
*rcp = 0;
|
||||
char c;
|
||||
char *ds = d;
|
||||
char *de = d + dlen;
|
||||
|
||||
while ((c = *p++)) {
|
||||
if (c == '"') { // string closing quotes
|
||||
if (end) {
|
||||
*end = p;
|
||||
}
|
||||
return (int) (d - ds);
|
||||
} else if (c == '\\') {
|
||||
switch (*p) {
|
||||
case '\\':
|
||||
case '/':
|
||||
case '"':
|
||||
if (d < de) {
|
||||
*d = *p;
|
||||
}
|
||||
++p, ++d;
|
||||
break;
|
||||
case 'b':
|
||||
if (d < de) {
|
||||
*d = '\b';
|
||||
}
|
||||
++p, ++d;
|
||||
break;
|
||||
case 'f':
|
||||
if (d < de) {
|
||||
*d = '\f';
|
||||
}
|
||||
++p, ++d;
|
||||
break;
|
||||
case 'n':
|
||||
case 'r':
|
||||
if (d < de) {
|
||||
*d = '\n';
|
||||
}
|
||||
++p, ++d;
|
||||
break;
|
||||
case 't':
|
||||
if (d < de) {
|
||||
*d = '\t';
|
||||
}
|
||||
++p, ++d;
|
||||
break;
|
||||
case 'u': {
|
||||
uint32_t cp, cp2;
|
||||
int h1, h2, h3, h4;
|
||||
if ( ((h1 = _jbl_hex(p[1])) < 0) || ((h2 = _jbl_hex(p[2])) < 0)
|
||||
|| ((h3 = _jbl_hex(p[3])) < 0) || ((h4 = _jbl_hex(p[4])) < 0)) {
|
||||
*rcp = JBL_ERROR_PARSE_INVALID_CODEPOINT;
|
||||
return 0;
|
||||
}
|
||||
cp = h1 << 12 | h2 << 8 | h3 << 4 | h4;
|
||||
if ((cp & 0xfc00) == 0xd800) {
|
||||
p += 6;
|
||||
if ( (p[-1] != '\\') || (*p != 'u')
|
||||
|| ((h1 = _jbl_hex(p[1])) < 0) || ((h2 = _jbl_hex(p[2])) < 0)
|
||||
|| ((h3 = _jbl_hex(p[3])) < 0) || ((h4 = _jbl_hex(p[4])) < 0)) {
|
||||
*rcp = JBL_ERROR_PARSE_INVALID_CODEPOINT;
|
||||
return 0;
|
||||
}
|
||||
cp2 = h1 << 12 | h2 << 8 | h3 << 4 | h4;
|
||||
if ((cp2 & 0xfc00) != 0xdc00) {
|
||||
*rcp = JBL_ERROR_PARSE_INVALID_CODEPOINT;
|
||||
return 0;
|
||||
}
|
||||
cp = 0x10000 + ((cp - 0xd800) << 10) + (cp2 - 0xdc00);
|
||||
}
|
||||
if (!utf8proc_codepoint_valid(cp)) {
|
||||
*rcp = JBL_ERROR_PARSE_INVALID_CODEPOINT;
|
||||
return 0;
|
||||
}
|
||||
uint8_t uchars[4];
|
||||
utf8proc_ssize_t ulen = utf8proc_encode_char(cp, uchars);
|
||||
assert(ulen <= sizeof(uchars));
|
||||
for (int i = 0; i < ulen; ++i) {
|
||||
if (d < de) {
|
||||
*d = uchars[i];
|
||||
}
|
||||
++d;
|
||||
}
|
||||
p += 5;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if (d < de) {
|
||||
*d = c;
|
||||
}
|
||||
++d;
|
||||
}
|
||||
} else {
|
||||
if (d < de) {
|
||||
*d = c;
|
||||
}
|
||||
++d;
|
||||
}
|
||||
}
|
||||
*rcp = JBL_ERROR_PARSE_UNQUOTED_STRING;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char* _jbl_parse_key(const char **key, const char *p, JCTX *ctx) {
|
||||
char c;
|
||||
*key = "";
|
||||
while ((c = *p++)) {
|
||||
if (c == '"') {
|
||||
int len = _jbl_unescape_json_string(p, 0, 0, 0, &ctx->rc);
|
||||
if (ctx->rc) {
|
||||
return 0;
|
||||
}
|
||||
if (len) {
|
||||
char *kptr = iwpool_alloc(len + 1, ctx->pool);
|
||||
if (!kptr) {
|
||||
ctx->rc = iwrc_set_errno(IW_ERROR_ALLOC, errno);
|
||||
return 0;
|
||||
}
|
||||
if ((len != _jbl_unescape_json_string(p, kptr, len, &p, &ctx->rc)) || ctx->rc) {
|
||||
if (!ctx->rc) {
|
||||
ctx->rc = JBL_ERROR_PARSE_JSON;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
kptr[len] = '\0';
|
||||
*key = kptr;
|
||||
}
|
||||
while (*p && IS_WHITESPACE(*p)) p++;
|
||||
if (*p == ':') {
|
||||
return p + 1;
|
||||
}
|
||||
ctx->rc = JBL_ERROR_PARSE_JSON;
|
||||
return 0;
|
||||
} else if (c == '}') {
|
||||
return p - 1;
|
||||
} else if (IS_WHITESPACE(c) || (c == ',')) {
|
||||
continue;
|
||||
} else {
|
||||
ctx->rc = JBL_ERROR_PARSE_JSON;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
ctx->rc = JBL_ERROR_PARSE_JSON;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char* _jbl_parse_value(
|
||||
int lvl,
|
||||
JBL_NODE parent,
|
||||
const char *key, int klidx,
|
||||
const char *p,
|
||||
JCTX *ctx
|
||||
) {
|
||||
if (lvl > JBL_MAX_NESTING_LEVEL) {
|
||||
ctx->rc = JBL_ERROR_MAX_NESTING_LEVEL_EXCEEDED;
|
||||
return 0;
|
||||
}
|
||||
|
||||
JBL_NODE node;
|
||||
while (1) {
|
||||
switch (*p) {
|
||||
case '\0':
|
||||
ctx->rc = JBL_ERROR_PARSE_JSON;
|
||||
return 0;
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\n':
|
||||
case '\r':
|
||||
case ',':
|
||||
++p;
|
||||
break;
|
||||
case 'n':
|
||||
if (!strncmp(p, "null", 4)) {
|
||||
_jbl_json_create_node(JBV_NULL, key, klidx, parent, ctx);
|
||||
if (ctx->rc) {
|
||||
return 0;
|
||||
}
|
||||
return p + 4;
|
||||
}
|
||||
ctx->rc = JBL_ERROR_PARSE_JSON;
|
||||
return 0;
|
||||
case 't':
|
||||
if (!strncmp(p, "true", 4)) {
|
||||
node = _jbl_json_create_node(JBV_BOOL, key, klidx, parent, ctx);
|
||||
if (ctx->rc) {
|
||||
return 0;
|
||||
}
|
||||
node->vbool = true; // -V522
|
||||
return p + 4;
|
||||
}
|
||||
ctx->rc = JBL_ERROR_PARSE_JSON;
|
||||
return 0;
|
||||
case 'f':
|
||||
if (!strncmp(p, "false", 5)) {
|
||||
node = _jbl_json_create_node(JBV_BOOL, key, klidx, parent, ctx);
|
||||
if (ctx->rc) {
|
||||
return 0;
|
||||
}
|
||||
node->vbool = false;
|
||||
return p + 5;
|
||||
}
|
||||
ctx->rc = JBL_ERROR_PARSE_JSON;
|
||||
return 0;
|
||||
case '"':
|
||||
++p;
|
||||
const char *end;
|
||||
int len = _jbl_unescape_json_string(p, 0, 0, &end, &ctx->rc);
|
||||
if (ctx->rc) {
|
||||
return 0;
|
||||
}
|
||||
node = _jbl_json_create_node(JBV_STR, key, klidx, parent, ctx);
|
||||
if (ctx->rc) {
|
||||
return 0;
|
||||
}
|
||||
if (len) {
|
||||
char *vptr = iwpool_alloc(len + 1, ctx->pool);
|
||||
if (!vptr) {
|
||||
ctx->rc = iwrc_set_errno(IW_ERROR_ALLOC, errno);
|
||||
return 0;
|
||||
}
|
||||
if ((len != _jbl_unescape_json_string(p, vptr, len, &p, &ctx->rc)) || ctx->rc) {
|
||||
if (!ctx->rc) {
|
||||
ctx->rc = JBL_ERROR_PARSE_JSON;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
vptr[len] = '\0';
|
||||
node->vptr = vptr;
|
||||
node->vsize = len;
|
||||
} else {
|
||||
p = end;
|
||||
node->vptr = "";
|
||||
node->vsize = 0;
|
||||
}
|
||||
return p;
|
||||
case '{':
|
||||
node = _jbl_json_create_node(JBV_OBJECT, key, klidx, parent, ctx);
|
||||
if (ctx->rc) {
|
||||
return 0;
|
||||
}
|
||||
++p;
|
||||
while (1) {
|
||||
const char *nkey;
|
||||
p = _jbl_parse_key(&nkey, p, ctx);
|
||||
if (ctx->rc) {
|
||||
return 0;
|
||||
}
|
||||
if (*p == '}') {
|
||||
return p + 1; // -V522
|
||||
}
|
||||
p = _jbl_parse_value(lvl + 1, node, nkey, (int) strlen(nkey), p, ctx);
|
||||
if (ctx->rc) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case '[':
|
||||
node = _jbl_json_create_node(JBV_ARRAY, key, klidx, parent, ctx);
|
||||
if (ctx->rc) {
|
||||
return 0;
|
||||
}
|
||||
++p;
|
||||
for (int i = 0; ; ++i) {
|
||||
p = _jbl_parse_value(lvl + 1, node, 0, i, p, ctx);
|
||||
if (ctx->rc) {
|
||||
return 0;
|
||||
}
|
||||
if (*p == ']') {
|
||||
return p + 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ']':
|
||||
return p;
|
||||
break;
|
||||
case '-':
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9': {
|
||||
node = _jbl_json_create_node(JBV_I64, key, klidx, parent, ctx);
|
||||
if (ctx->rc) {
|
||||
return 0;
|
||||
}
|
||||
char *pe;
|
||||
node->vi64 = strtoll(p, &pe, 0);
|
||||
if ((pe == p) || (errno == ERANGE)) {
|
||||
ctx->rc = JBL_ERROR_PARSE_JSON;
|
||||
return 0;
|
||||
}
|
||||
if ((*pe == '.') || (*pe == 'e') || (*pe == 'E')) {
|
||||
node->type = JBV_F64;
|
||||
node->vf64 = iwstrtod(p, &pe);
|
||||
if ((pe == p) || (errno == ERANGE)) {
|
||||
ctx->rc = JBL_ERROR_PARSE_JSON;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return pe;
|
||||
}
|
||||
default:
|
||||
ctx->rc = JBL_ERROR_PARSE_JSON;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
static iwrc _jbl_node_as_json(JBL_NODE node, jbl_json_printer pt, void *op, int lvl, jbl_print_flags_t pf) {
|
||||
iwrc rc = 0;
|
||||
bool pretty = pf & JBL_PRINT_PRETTY;
|
||||
|
||||
#define PT(data_, size_, ch_, count_) do { \
|
||||
rc = pt(data_, size_, ch_, count_, op); \
|
||||
RCRET(rc); \
|
||||
} while (0)
|
||||
|
||||
switch (node->type) {
|
||||
case JBV_ARRAY:
|
||||
PT(0, 0, '[', 1);
|
||||
if (node->child && pretty) {
|
||||
PT(0, 0, '\n', 1);
|
||||
}
|
||||
for (JBL_NODE n = node->child; n; n = n->next) {
|
||||
if (pretty) {
|
||||
PT(0, 0, ' ', lvl + 1);
|
||||
}
|
||||
rc = _jbl_node_as_json(n, pt, op, lvl + 1, pf);
|
||||
RCRET(rc);
|
||||
if (n->next) {
|
||||
PT(0, 0, ',', 1);
|
||||
}
|
||||
if (pretty) {
|
||||
PT(0, 0, '\n', 1);
|
||||
}
|
||||
}
|
||||
if (node->child && pretty) {
|
||||
PT(0, 0, ' ', lvl);
|
||||
}
|
||||
PT(0, 0, ']', 1);
|
||||
break;
|
||||
case JBV_OBJECT:
|
||||
PT(0, 0, '{', 1);
|
||||
if (node->child && pretty) {
|
||||
PT(0, 0, '\n', 1);
|
||||
}
|
||||
for (JBL_NODE n = node->child; n; n = n->next) {
|
||||
if (pretty) {
|
||||
PT(0, 0, ' ', lvl + 1);
|
||||
}
|
||||
rc = _jbl_write_string(n->key, n->klidx, pt, op, pf);
|
||||
RCRET(rc);
|
||||
if (pretty) {
|
||||
PT(": ", -1, 0, 0);
|
||||
} else {
|
||||
PT(0, 0, ':', 1);
|
||||
}
|
||||
rc = _jbl_node_as_json(n, pt, op, lvl + 1, pf);
|
||||
RCRET(rc);
|
||||
if (n->next) {
|
||||
PT(0, 0, ',', 1);
|
||||
}
|
||||
if (pretty) {
|
||||
PT(0, 0, '\n', 1);
|
||||
}
|
||||
}
|
||||
if (node->child && pretty) {
|
||||
PT(0, 0, ' ', lvl);
|
||||
}
|
||||
PT(0, 0, '}', 1);
|
||||
break;
|
||||
case JBV_STR:
|
||||
rc = _jbl_write_string(node->vptr, node->vsize, pt, op, pf);
|
||||
break;
|
||||
case JBV_I64:
|
||||
rc = _jbl_write_int(node->vi64, pt, op);
|
||||
break;
|
||||
case JBV_F64:
|
||||
rc = _jbl_write_double(node->vf64, pt, op);
|
||||
break;
|
||||
case JBV_BOOL:
|
||||
if (node->vbool) {
|
||||
PT("true", 4, 0, 1);
|
||||
} else {
|
||||
PT("false", 5, 0, 1);
|
||||
}
|
||||
break;
|
||||
case JBV_NULL:
|
||||
PT("null", 4, 0, 1);
|
||||
break;
|
||||
default:
|
||||
iwlog_ecode_error3(IW_ERROR_ASSERTION);
|
||||
return IW_ERROR_ASSERTION;
|
||||
}
|
||||
#undef PT
|
||||
return rc;
|
||||
}
|
||||
|
||||
static JBL_NODE _jbl_clone_node_struct(JBL_NODE src, IWPOOL *pool) {
|
||||
iwrc rc;
|
||||
JBL_NODE n = iwpool_calloc(sizeof(*n), pool);
|
||||
if (!n) {
|
||||
return 0;
|
||||
}
|
||||
n->vsize = src->vsize;
|
||||
n->type = src->type;
|
||||
n->klidx = src->klidx;
|
||||
n->flags = src->flags;
|
||||
|
||||
if (src->key) {
|
||||
n->key = iwpool_strndup(pool, src->key, src->klidx, &rc);
|
||||
if (!n->key) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
switch (src->type) {
|
||||
case JBV_STR: {
|
||||
n->vptr = iwpool_strndup(pool, src->vptr, src->vsize, &rc);
|
||||
if (!n->vptr) {
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case JBV_I64:
|
||||
n->vi64 = src->vi64;
|
||||
break;
|
||||
case JBV_BOOL:
|
||||
n->vbool = src->vbool;
|
||||
break;
|
||||
case JBV_F64:
|
||||
n->vf64 = src->vf64;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
;
|
||||
return n;
|
||||
}
|
||||
|
||||
static jbn_visitor_cmd_t _jbl_clone_node_visit(
|
||||
int lvl, JBL_NODE n, const char *key, int klidx, JBN_VCTX *vctx,
|
||||
iwrc *rc
|
||||
) {
|
||||
if (lvl < 0) {
|
||||
return JBL_VCMD_OK;
|
||||
}
|
||||
JBL_NODE parent = vctx->root;
|
||||
if (lvl < vctx->pos) { // Pop
|
||||
for ( ; lvl < vctx->pos; --vctx->pos) {
|
||||
parent = parent->parent;
|
||||
assert(parent);
|
||||
}
|
||||
vctx->root = parent;
|
||||
assert(vctx->root);
|
||||
} else if (lvl > vctx->pos) { // Push
|
||||
vctx->pos = lvl;
|
||||
parent = vctx->op;
|
||||
vctx->root = parent;
|
||||
assert(parent);
|
||||
}
|
||||
JBL_NODE nn = _jbl_clone_node_struct(n, vctx->pool);
|
||||
if (!nn) {
|
||||
*rc = iwrc_set_errno(IW_ERROR_ALLOC, errno);
|
||||
return JBL_VCMD_TERMINATE;
|
||||
}
|
||||
_jbn_add_item(parent, nn);
|
||||
if (nn->type >= JBV_OBJECT) {
|
||||
vctx->op = nn; // Remeber the last container object
|
||||
}
|
||||
return JBL_VCMD_OK;
|
||||
}
|
||||
|
||||
iwrc jbn_clone(JBL_NODE src, JBL_NODE *targetp, IWPOOL *pool) {
|
||||
*targetp = 0;
|
||||
JBL_NODE n = _jbl_clone_node_struct(src, pool);
|
||||
if (!n) {
|
||||
return iwrc_set_errno(IW_ERROR_ALLOC, errno);
|
||||
}
|
||||
JBN_VCTX vctx = {
|
||||
.pool = pool,
|
||||
.root = n,
|
||||
.op = n
|
||||
};
|
||||
iwrc rc = jbn_visit(src, 0, &vctx, _jbl_clone_node_visit);
|
||||
RCRET(rc);
|
||||
*targetp = n;
|
||||
return 0;
|
||||
}
|
||||
|
||||
iwrc jbn_as_json(JBL_NODE node, jbl_json_printer pt, void *op, jbl_print_flags_t pf) {
|
||||
return _jbl_node_as_json(node, pt, op, 0, pf);
|
||||
}
|
||||
|
||||
iwrc jbn_from_json(const char *json, JBL_NODE *node, IWPOOL *pool) {
|
||||
*node = 0;
|
||||
JCTX ctx = {
|
||||
.pool = pool,
|
||||
.buf = json
|
||||
};
|
||||
_jbl_skip_bom(&ctx);
|
||||
_jbl_parse_value(0, 0, 0, 0, ctx.buf, &ctx);
|
||||
*node = ctx.root;
|
||||
return ctx.rc;
|
||||
}
|
||||
|
||||
+2989
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,888 @@
|
||||
#pragma once
|
||||
#ifndef IWJSON_H
|
||||
#define IWJSON_H
|
||||
|
||||
/**************************************************************************************************
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2012-2022 Softmotions Ltd <info@softmotions.com>
|
||||
*
|
||||
* 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.
|
||||
*************************************************************************************************/
|
||||
|
||||
/** @file
|
||||
*
|
||||
* @brief JSON serialization and patching routines.
|
||||
*
|
||||
* Supported standards:
|
||||
*
|
||||
* - [JSON Patch](https://tools.ietf.org/html/rfc6902)
|
||||
* - [JSON Merge patch](https://tools.ietf.org/html/rfc7386)
|
||||
* - [JSON Path specification](https://tools.ietf.org/html/rfc6901)
|
||||
*
|
||||
* JSON document can be represented in three different formats:
|
||||
*
|
||||
* - Plain JSON text.
|
||||
*
|
||||
* - @ref JBL Memory compact binary format [Binn](https://github.com/liteserver/binn)
|
||||
* Used for JSON serialization but lacks of data modification flexibility.
|
||||
*
|
||||
* - @ref JBL_NODE In memory JSON document presented as tree. Convenient for in-place
|
||||
* document modification and patching.
|
||||
*
|
||||
* Library function allows conversion of JSON document between above formats.
|
||||
*/
|
||||
|
||||
#include "iwlog.h"
|
||||
#include "iwpool.h"
|
||||
#include "iwxstr.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
IW_EXTERN_C_START
|
||||
|
||||
/**
|
||||
* @brief JSON document in compact binary format [Binn](https://github.com/liteserver/binn)
|
||||
*/
|
||||
struct _JBL;
|
||||
typedef struct _JBL*JBL;
|
||||
|
||||
typedef enum {
|
||||
_JBL_ERROR_START = (IW_ERROR_START + 6000UL),
|
||||
JBL_ERROR_INVALID_BUFFER, /**< Invalid JBL buffer (JBL_ERROR_INVALID_BUFFER) */
|
||||
JBL_ERROR_CREATION, /**< Cannot create JBL object (JBL_ERROR_CREATION) */
|
||||
JBL_ERROR_INVALID, /**< Invalid JBL object (JBL_ERROR_INVALID) */
|
||||
JBL_ERROR_PARSE_JSON, /**< Failed to parse JSON string (JBL_ERROR_PARSE_JSON) */
|
||||
JBL_ERROR_PARSE_UNQUOTED_STRING, /**< Unquoted JSON string (JBL_ERROR_PARSE_UNQUOTED_STRING) */
|
||||
JBL_ERROR_PARSE_INVALID_CODEPOINT,
|
||||
/**< Invalid unicode codepoint/escape sequence
|
||||
(JBL_ERROR_PARSE_INVALID_CODEPOINT) */
|
||||
JBL_ERROR_PARSE_INVALID_UTF8, /**< Invalid utf8 string (JBL_ERROR_PARSE_INVALID_UTF8) */
|
||||
JBL_ERROR_JSON_POINTER, /**< Invalid JSON pointer (rfc6901) path (JBL_ERROR_JSON_POINTER) */
|
||||
JBL_ERROR_PATH_NOTFOUND, /**< JSON object not matched the path specified (JBL_ERROR_PATH_NOTFOUND) */
|
||||
JBL_ERROR_PATCH_INVALID, /**< Invalid JSON patch specified (JBL_ERROR_PATCH_INVALID) */
|
||||
JBL_ERROR_PATCH_INVALID_OP, /**< Invalid JSON patch operation specified (JBL_ERROR_PATCH_INVALID_OP) */
|
||||
JBL_ERROR_PATCH_NOVALUE, /**< No value specified in JSON patch (JBL_ERROR_PATCH_NOVALUE) */
|
||||
JBL_ERROR_PATCH_TARGET_INVALID,
|
||||
/**< Could not find target object to set value (JBL_ERROR_PATCH_TARGET_INVALID)
|
||||
*/
|
||||
JBL_ERROR_PATCH_INVALID_VALUE, /**< Invalid value specified by patch (JBL_ERROR_PATCH_INVALID_VALUE) */
|
||||
JBL_ERROR_PATCH_INVALID_ARRAY_INDEX,
|
||||
/**< Invalid array index in JSON patch path
|
||||
(JBL_ERROR_PATCH_INVALID_ARRAY_INDEX) */
|
||||
JBL_ERROR_NOT_AN_OBJECT, /**< JBL is not an object (JBL_ERROR_NOT_AN_OBJECT) */
|
||||
JBL_ERROR_TYPE_MISMATCHED,
|
||||
/**< Type of JBL object mismatched user type constraints
|
||||
(JBL_ERROR_TYPE_MISMATCHED) */
|
||||
JBL_ERROR_PATCH_TEST_FAILED, /**< JSON patch test operation failed (JBL_ERROR_PATCH_TEST_FAILED) */
|
||||
JBL_ERROR_MAX_NESTING_LEVEL_EXCEEDED,
|
||||
/**< Reached the maximal object nesting level: 1000
|
||||
(JBL_ERROR_MAX_NESTING_LEVEL_EXCEEDED) */
|
||||
_JBL_ERROR_END,
|
||||
} jbl_ecode_t;
|
||||
|
||||
typedef struct _JBL_iterator {
|
||||
unsigned char *pnext;
|
||||
unsigned char *plimit;
|
||||
int type;
|
||||
int count;
|
||||
int current;
|
||||
} JBL_iterator;
|
||||
|
||||
typedef uint8_t jbl_print_flags_t;
|
||||
#define JBL_PRINT_PRETTY ((jbl_print_flags_t) 0x01U)
|
||||
#define JBL_PRINT_CODEPOINTS ((jbl_print_flags_t) 0x02U)
|
||||
|
||||
typedef uint8_t jbn_visitor_cmd_t;
|
||||
#define JBL_VCMD_OK ((jbn_visitor_cmd_t) 0x00U)
|
||||
#define JBL_VCMD_TERMINATE ((jbn_visitor_cmd_t) 0x01U)
|
||||
#define JBL_VCMD_SKIP_NESTED ((jbn_visitor_cmd_t) 0x02U)
|
||||
#define JBN_VCMD_DELETE ((jbn_visitor_cmd_t) 0x04U)
|
||||
|
||||
|
||||
typedef enum {
|
||||
JBV_NONE = 0, // Do not reorder
|
||||
JBV_NULL,
|
||||
JBV_BOOL, // Do not reorder
|
||||
JBV_I64,
|
||||
JBV_F64,
|
||||
JBV_STR,
|
||||
JBV_OBJECT, // Do not reorder
|
||||
JBV_ARRAY,
|
||||
} jbl_type_t;
|
||||
|
||||
/**
|
||||
* @brief JSON document as in-memory tree (DOM tree).
|
||||
*/
|
||||
typedef struct _JBL_NODE {
|
||||
struct _JBL_NODE *next;
|
||||
struct _JBL_NODE *prev;
|
||||
struct _JBL_NODE *parent; /**< Optional parent */
|
||||
const char *key;
|
||||
int klidx;
|
||||
uint32_t flags; /**< Utility node flags */
|
||||
|
||||
// Do not sort/add members after this point (offsetof usage below)
|
||||
struct _JBL_NODE *child;
|
||||
int vsize;
|
||||
jbl_type_t type;
|
||||
union {
|
||||
const char *vptr;
|
||||
bool vbool;
|
||||
int64_t vi64;
|
||||
double vf64;
|
||||
};
|
||||
} *JBL_NODE;
|
||||
|
||||
/**
|
||||
* @brief JSON Patch operation according to rfc6902
|
||||
*/
|
||||
typedef enum {
|
||||
JBP_ADD = 1,
|
||||
JBP_REMOVE,
|
||||
JBP_REPLACE,
|
||||
JBP_COPY,
|
||||
JBP_MOVE,
|
||||
JBP_TEST,
|
||||
// Non standard operations
|
||||
JBP_INCREMENT, /**< Value increment */
|
||||
JBP_ADD_CREATE, /**< Create intermediate object nodes for missing path segments */
|
||||
JBP_SWAP, /**< Swap values of two nodes */
|
||||
} jbp_patch_t;
|
||||
|
||||
/**
|
||||
* @brief JSON patch specification
|
||||
*/
|
||||
typedef struct _JBL_PATCH {
|
||||
jbp_patch_t op;
|
||||
const char *path;
|
||||
const char *from;
|
||||
const char *vjson;
|
||||
JBL_NODE vnode;
|
||||
} JBL_PATCH;
|
||||
|
||||
/**
|
||||
* @brief JSON pointer rfc6901
|
||||
* @see jbl_ptr_alloc()
|
||||
*/
|
||||
typedef struct _JBL_PTR {
|
||||
uint64_t op; /**< Opaque data associated with pointer */
|
||||
int cnt; /**< Number of nodes */
|
||||
int sz; /**< Size of JBL_PTR allocated area */
|
||||
char *n[1]; /**< Path nodes */
|
||||
} *JBL_PTR;
|
||||
|
||||
/** Prints JSON to some oputput specified by `op` */
|
||||
typedef iwrc (*jbl_json_printer)(const char *data, int size, char ch, int count, void *op);
|
||||
|
||||
IW_EXPORT void iwjson_ftoa(long double val, char buf[IWNUMBUF_SIZE], size_t *out_len);
|
||||
|
||||
/**
|
||||
* @brief Create empty binary JSON object.
|
||||
*
|
||||
* @note `jblp` should be disposed by `jbl_destroy()`
|
||||
* @see `jbl_fill_from_node()`
|
||||
* @param [out] jblp Pointer to be initialized by new object.
|
||||
*/
|
||||
IW_EXPORT WUR iwrc jbl_create_empty_object(JBL *jblp);
|
||||
|
||||
/**
|
||||
* @brief Create empty binary JSON array.
|
||||
*
|
||||
* @note `jblp` should be disposed by `jbl_destroy()`
|
||||
* @see `jbl_fill_from_node()`
|
||||
* @param [out] jblp Pointer to be initialized by new object.
|
||||
*/
|
||||
IW_EXPORT WUR iwrc jbl_create_empty_array(JBL *jblp);
|
||||
|
||||
/**
|
||||
* @brief Sets arbitrary user data associated with JBL object.
|
||||
*
|
||||
* @param jbl JBL container
|
||||
* @param user_data User data pointer. Optional.
|
||||
* @param user_data_free_fn User data dispose function. Optional.
|
||||
*/
|
||||
IW_EXPORT void jbl_set_user_data(JBL jbl, void *user_data, void (*user_data_free_fn)(void*));
|
||||
|
||||
/**
|
||||
* @brief Returns user data associated with given `jbl` container.
|
||||
*
|
||||
* @param jbl JBL container.
|
||||
*/
|
||||
IW_EXPORT void* jbl_get_user_data(JBL jbl);
|
||||
|
||||
/**
|
||||
* @brief Set integer JBL object property value
|
||||
* or add a new entry to end of array JBL object.
|
||||
*
|
||||
* In the case when `jbl` object is array value will be added to end array.
|
||||
*
|
||||
* @warning `jbl` object must writable in other words created with
|
||||
* `jbl_create_empty_object()` or `jbl_create_empty_array()`
|
||||
*
|
||||
* @param jbl JBL container
|
||||
* @param key Object key. Does't makes sense for array objects.
|
||||
* @param v Value to set
|
||||
*/
|
||||
IW_EXPORT iwrc jbl_set_int64(JBL jbl, const char *key, int64_t v);
|
||||
|
||||
/**
|
||||
* @brief Set double JBL object property value
|
||||
* or add a new entry to end of array JBL object.
|
||||
*
|
||||
* In the case when `jbl` object is array value will be added to end array.
|
||||
*
|
||||
* @warning `jbl` object must writable in other words created with
|
||||
* `jbl_create_empty_object()` or `jbl_create_empty_array()`
|
||||
*
|
||||
* @param jbl JBL container
|
||||
* @param key Object key. Does't makes sense for array objects.
|
||||
* @param v Value to set
|
||||
*/
|
||||
IW_EXPORT iwrc jbl_set_f64(JBL jbl, const char *key, double v);
|
||||
|
||||
/**
|
||||
* @brief Set string JBL object property value
|
||||
* or add a new entry to end of array JBL object.
|
||||
*
|
||||
* In the case when `jbl` object is array value will be added to end array.
|
||||
*
|
||||
* @warning `jbl` object must writable in other words created with
|
||||
* `jbl_create_empty_object()` or `jbl_create_empty_array()`
|
||||
*
|
||||
* @param jbl JBL container
|
||||
* @param key Object key. Does't makes sense for array objects.
|
||||
* @param v Value to set
|
||||
*/
|
||||
IW_EXPORT iwrc jbl_set_string(JBL jbl, const char *key, const char *v);
|
||||
|
||||
IW_EXPORT iwrc jbl_set_string_printf(JBL jbl, const char *key, const char *format, ...);
|
||||
|
||||
/**
|
||||
* @brief Set bool JBL object property value
|
||||
* or add a new entry to end of array JBL object.
|
||||
*
|
||||
* In the case when `jbl` object is array value will be added to end array.
|
||||
*
|
||||
* @warning `jbl` object must writable in other words created with
|
||||
* `jbl_create_empty_object()` or `jbl_create_empty_array()`
|
||||
*
|
||||
* @param jbl JBL container
|
||||
* @param key Object key. Does't makes sense for array objects.
|
||||
* @param v Value to set
|
||||
*/
|
||||
IW_EXPORT iwrc jbl_set_bool(JBL jbl, const char *key, bool v);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Set null JBL object property value
|
||||
* or add a new entry to end of array JBL object.
|
||||
*
|
||||
* In the case when `jbl` object is array value will be added to end array.
|
||||
*
|
||||
* @warning `jbl` object must writable in other words created with
|
||||
* `jbl_create_empty_object()` or `jbl_create_empty_array()`
|
||||
*
|
||||
* @param jbl JBL container
|
||||
* @param key Object key. Does't makes sense for array objects.
|
||||
* @param v Value to set
|
||||
*/
|
||||
IW_EXPORT iwrc jbl_set_null(JBL jbl, const char *key);
|
||||
|
||||
IW_EXPORT iwrc jbl_set_empty_array(JBL jbl, const char *key);
|
||||
|
||||
IW_EXPORT iwrc jbl_set_empty_object(JBL jbl, const char *key);
|
||||
|
||||
/**
|
||||
* @brief Set nested JBL object property value
|
||||
* or add a new entry to end of array JBL object.
|
||||
*
|
||||
* In the case when `jbl` object is array value will be added to end array.
|
||||
*
|
||||
* @warning `jbl` object must writable in other words created with
|
||||
* `jbl_create_empty_object()` or `jbl_create_empty_array()`
|
||||
*
|
||||
* @param jbl JBL container
|
||||
* @param key Object key. Does't makes sense for array objects.
|
||||
* @param v Value to set
|
||||
*/
|
||||
IW_EXPORT iwrc jbl_set_nested(JBL jbl, const char *key, JBL nested);
|
||||
|
||||
/**
|
||||
* @brief Initialize new `JBL` document by `binn` data from buffer.
|
||||
* @note Created document will be allocated by `malloc()`
|
||||
* and should be destroyed by `jbl_destroy()`.
|
||||
*
|
||||
* @param [out] jblp Pointer initialized by created JBL document. Not zero.
|
||||
* @param buf Memory buffer with `binn` data. Not zero.
|
||||
* @param bufsz Size of `buf`
|
||||
* @param keep_on_destroy If true `buf` not will be freed by `jbl_destroy()`
|
||||
*/
|
||||
IW_EXPORT iwrc jbl_from_buf_keep(JBL *jblp, void *buf, size_t bufsz, bool keep_on_destroy);
|
||||
|
||||
/**
|
||||
* @brief Clones the given `src` JBL object into newly allocated `targetp` object.
|
||||
*
|
||||
* JBL object stored into `targetp` should be disposed by `jbl_destroy()`.
|
||||
*
|
||||
* @param src Source object to clone
|
||||
* @param targetp Pointer on target object.
|
||||
*/
|
||||
IW_EXPORT iwrc jbl_clone(JBL src, JBL *targetp);
|
||||
|
||||
/**
|
||||
* @brief Copy all keys from `src` object into `target` object.
|
||||
* @note Function does not care about keys duplication.
|
||||
*
|
||||
* @param src Source JBL object. Must be object.
|
||||
* @param target Target JBL object. Must be object.
|
||||
*/
|
||||
IW_EXPORT iwrc jbl_object_copy_to(JBL src, JBL target);
|
||||
|
||||
/**
|
||||
* @brief Clones the given `src` JBL_NODE object into new `targetp` instance.
|
||||
* Memory allocateted by given memor `pool` instance.
|
||||
*
|
||||
* @param src Source object to clone
|
||||
* @param target Pointer on new instance
|
||||
* @param pool Memory pool used for allocations during clone object construction
|
||||
*/
|
||||
IW_EXPORT iwrc jbn_clone(JBL_NODE src, JBL_NODE *targetp, IWPOOL *pool);
|
||||
|
||||
/**
|
||||
* @brief Assign a JSON node value from `from` node into `target` node.
|
||||
* Context elements of `target` node: `parent`, `next` are not touched.
|
||||
*
|
||||
* @param target Node
|
||||
* @param from
|
||||
* @return IW_EXPORT jbn_apply_from
|
||||
*/
|
||||
IW_EXPORT void jbn_apply_from(JBL_NODE target, JBL_NODE from);
|
||||
|
||||
/**
|
||||
* @brief Copies JSON subtree under given `src_path` into `target` object under `target_path`.
|
||||
* If some tree exists under `target_path` it will be replaced by copied subtree.
|
||||
*
|
||||
* Copied subtree will be allocated in using given memory `pool`.
|
||||
*
|
||||
* @param src Source JSON tree.
|
||||
* @param src_path Path where copied subtree located. If src_path is `/` then `src` object itself will be cloned.
|
||||
* @param target Target JSON tree.
|
||||
* @param target_path Path to place copied subtree.
|
||||
* @param overwrite_on_nulls If true `null` values will be copied to `src` object as well.
|
||||
* @param no_src_clone If true object pointed by `src_path` object will not be cloned into `pool` before applying patch.
|
||||
* It is a dangerous option if you use same memory pool for source and target objects.
|
||||
* Do not set it to `true` until you clearly understand what are you doing.
|
||||
* @param pool Memory pool used for allocations
|
||||
*/
|
||||
IW_EXPORT iwrc jbn_copy_path(
|
||||
JBL_NODE src,
|
||||
const char *src_path,
|
||||
JBL_NODE target,
|
||||
const char *target_path,
|
||||
bool overwrite_on_nulls,
|
||||
bool no_src_clone,
|
||||
IWPOOL *pool);
|
||||
|
||||
/**
|
||||
* @brief Copies a set of values pointed by `paths` zero terminated array
|
||||
* of `src` object into respective paths of `target` object.
|
||||
*
|
||||
* @param src Source object whose keys will be copied.
|
||||
* @param target Target object to recieve key values of `src` obejct
|
||||
* @param paths Zero terminated array of pointers to zero terminated key names.
|
||||
* @param overwrite_on_nulls If true `null` values will be copied to `src` object as well.
|
||||
* @param no_src_clone If true copied objects will not be cloned into given `pool` before copying.
|
||||
* It is a dangerous option if you use same memory pool for source and target objects.
|
||||
* Do not set it to `true` until you clearly understand what are you doing.
|
||||
* @param pool Memory pool used for allocations
|
||||
*/
|
||||
IW_EXPORT iwrc jbn_copy_paths(
|
||||
JBL_NODE src,
|
||||
JBL_NODE target,
|
||||
const char **paths,
|
||||
bool overwrite_on_nulls,
|
||||
bool no_src_clone,
|
||||
IWPOOL *pool);
|
||||
|
||||
/**
|
||||
* @brief Clones a given `src` JBL object and stores it in memory allocated from `pool`.
|
||||
*
|
||||
* @param src Source object to clone
|
||||
* @param targetp Pointer on target object
|
||||
* @param pool Memory pool
|
||||
*/
|
||||
IW_EXPORT iwrc jbl_clone_into_pool(JBL src, JBL *targetp, IWPOOL *pool);
|
||||
|
||||
/**
|
||||
* @brief Constructs new `JBL` object from JSON string.
|
||||
* @note `jblp` should be disposed by `jbl_destroy()`
|
||||
* @param [out] jblp Pointer initialized by created JBL document. Not zero.
|
||||
* @param jsonstr JSON string to be converted
|
||||
*/
|
||||
IW_EXPORT iwrc jbl_from_json(JBL *jblp, const char *jsonstr);
|
||||
|
||||
|
||||
IW_EXPORT iwrc jbl_from_json_printf(JBL *jblp, const char *format, ...);
|
||||
|
||||
IW_EXPORT iwrc jbl_from_json_printf_va(JBL *jblp, const char *format, va_list va);
|
||||
|
||||
/**
|
||||
* @brief Get type of `jbl` value.
|
||||
*/
|
||||
IW_EXPORT jbl_type_t jbl_type(JBL jbl);
|
||||
|
||||
/**
|
||||
* @brief Get number of child elements in `jbl` container (object/array) or zero.
|
||||
*/
|
||||
IW_EXPORT size_t jbl_count(JBL jbl);
|
||||
|
||||
/**
|
||||
* @brief Get size of undelying data buffer of `jbl` value passed.
|
||||
*/
|
||||
IW_EXPORT size_t jbl_size(JBL jbl);
|
||||
|
||||
/**
|
||||
* @brief Returns size of JBL underlying data structure
|
||||
*/
|
||||
IW_EXPORT size_t jbl_structure_size(void);
|
||||
|
||||
IW_EXPORT iwrc jbl_from_buf_keep_onstack(JBL jbl, void *buf, size_t bufsz);
|
||||
|
||||
/**
|
||||
* @brief Interpret `jbl` value as `int32_t`.
|
||||
* Returns zero if value cannot be converted.
|
||||
*/
|
||||
IW_EXPORT int32_t jbl_get_i32(JBL jbl);
|
||||
|
||||
/**
|
||||
* @brief Interpret `jbl` value as `int64_t`.
|
||||
* Returns zero if value cannot be converted.
|
||||
*/
|
||||
IW_EXPORT int64_t jbl_get_i64(JBL jbl);
|
||||
|
||||
/**
|
||||
* @brief Interpret `jbl` value as `double` value.
|
||||
* Returns zero if value cannot be converted.
|
||||
*/
|
||||
IW_EXPORT double jbl_get_f64(JBL jbl);
|
||||
|
||||
/**
|
||||
* @brief Interpret `jbl` value as `\0` terminated character array.
|
||||
* Returns zero if value cannot be converted.
|
||||
*/
|
||||
IW_EXPORT const char* jbl_get_str(JBL jbl);
|
||||
|
||||
IW_EXPORT iwrc jbl_object_get_i64(JBL jbl, const char *key, int64_t *out);
|
||||
|
||||
IW_EXPORT iwrc jbl_object_get_f64(JBL jbl, const char *key, double *out);
|
||||
|
||||
IW_EXPORT iwrc jbl_object_get_bool(JBL jbl, const char *key, bool *out);
|
||||
|
||||
IW_EXPORT iwrc jbl_object_get_str(JBL jbl, const char *key, const char **out);
|
||||
|
||||
IW_EXPORT iwrc jbl_object_get_fill_jbl(JBL jbl, const char *key, JBL out);
|
||||
|
||||
IW_EXPORT jbl_type_t jbl_object_get_type(JBL jbl, const char *key);
|
||||
|
||||
/**
|
||||
* @brief Same as `jbl_get_str()` but copies at most `bufsz` into target `buf`.
|
||||
* Target buffer not touched if `jbl` value cannot be converted.
|
||||
*/
|
||||
IW_EXPORT size_t jbl_copy_strn(JBL jbl, char *buf, size_t bufsz);
|
||||
|
||||
/**
|
||||
* @brief Finds value in `jbl` document pointed by rfc6901 `path` and store it into `res`.
|
||||
*
|
||||
* @note `res` should be disposed by `jbl_destroy()`.
|
||||
* @note If value is not fount `res` will be set to zero.
|
||||
* @param jbl JBL document. Not zero.
|
||||
* @param path rfc6901 JSON pointer. Not zero.
|
||||
* @param [out] res Output value holder
|
||||
*/
|
||||
IW_EXPORT iwrc jbl_at(JBL jbl, const char *path, JBL *res);
|
||||
|
||||
IW_EXPORT iwrc jbn_at(JBL_NODE node, const char *path, JBL_NODE *res);
|
||||
|
||||
IW_EXPORT int jbn_path_compare(JBL_NODE n1, JBL_NODE n2, const char *path, jbl_type_t vtype, iwrc *rcp);
|
||||
|
||||
IW_EXPORT int jbn_paths_compare(
|
||||
JBL_NODE n1, const char *n1path, JBL_NODE n2, const char *n2path, jbl_type_t vtype,
|
||||
iwrc *rcp);
|
||||
|
||||
IW_EXPORT int jbn_path_compare_str(JBL_NODE n, const char *path, const char *sv, iwrc *rcp);
|
||||
|
||||
IW_EXPORT int jbn_path_compare_i64(JBL_NODE n, const char *path, int64_t iv, iwrc *rcp);
|
||||
|
||||
IW_EXPORT int jbn_path_compare_f64(JBL_NODE n, const char *path, double fv, iwrc *rcp);
|
||||
|
||||
IW_EXPORT int jbn_path_compare_bool(JBL_NODE n, const char *path, bool bv, iwrc *rcp);
|
||||
|
||||
/**
|
||||
* @brief @brief Finds value in `jbl` document pointed by `jp` structure and store it into `res`.
|
||||
*
|
||||
* @note `res` should be disposed by `jbl_destroy()`.
|
||||
* @note If value is not fount `res` will be set to zero.
|
||||
* @see `jbl_ptr_alloc()`
|
||||
* @param jbl JBL document. Not zero.
|
||||
* @param jp JSON pointer.
|
||||
* @param [out] res Output value holder
|
||||
*/
|
||||
IW_EXPORT iwrc jbl_at2(JBL jbl, JBL_PTR jp, JBL *res);
|
||||
|
||||
IW_EXPORT iwrc jbn_at2(JBL_NODE node, JBL_PTR jp, JBL_NODE *res);
|
||||
|
||||
/**
|
||||
* @brief Represent `jbl` document as raw data buffer.
|
||||
*
|
||||
* @note Caller do not require release `buf` explicitly.
|
||||
* @param jbl JBL document. Not zero.
|
||||
* @param [out] buf Pointer to data buffer. Not zero.
|
||||
* @param [out] size Pointer to data buffer size. Not zero.
|
||||
*/
|
||||
IW_EXPORT iwrc jbl_as_buf(JBL jbl, void **buf, size_t *size);
|
||||
|
||||
/**
|
||||
* @brief Prints JBL document as JSON string.
|
||||
*
|
||||
* @see jbl_fstream_json_printer()
|
||||
* @see jbl_xstr_json_printer()
|
||||
* @see jbl_count_json_printer()
|
||||
*
|
||||
* @param jbl JBL document. Not zero.
|
||||
* @param pt JSON printer function pointer. Not zero.
|
||||
* @param op Pointer to user data for JSON printer function.
|
||||
* @param pf JSON printing mode.
|
||||
*/
|
||||
IW_EXPORT iwrc jbl_as_json(JBL jbl, jbl_json_printer pt, void *op, jbl_print_flags_t pf);
|
||||
|
||||
/**
|
||||
* @brief JSON printer to stdlib `FILE*`pointer. Eg: `stderr`, `stdout`
|
||||
* @param op `FILE*` pointer
|
||||
*/
|
||||
IW_EXPORT iwrc jbl_fstream_json_printer(const char *data, int size, char ch, int count, void *op);
|
||||
|
||||
/**
|
||||
* @brief JSON printer to extended string buffer `IWXSTR`
|
||||
* @param op `IWXSTR*` pointer
|
||||
*/
|
||||
IW_EXPORT iwrc jbl_xstr_json_printer(const char *data, int size, char ch, int count, void *op);
|
||||
|
||||
/**
|
||||
* @brief Just counts bytes in JSON text.
|
||||
* @param op `int*` Pointer to counter number.
|
||||
*/
|
||||
IW_EXPORT iwrc jbl_count_json_printer(const char *data, int size, char ch, int count, void *op);
|
||||
|
||||
/**
|
||||
* @brief Destroys JBL document and releases its heap resources.
|
||||
* @note Will set `jblp` to zero.
|
||||
* @param jblp Pointer holder of JBL document. Not zero.
|
||||
*/
|
||||
IW_EXPORT void jbl_destroy(JBL *jblp);
|
||||
|
||||
/**
|
||||
* @brief Initializes placeholder for jbl iteration.
|
||||
* Must be freed by `jbl_destroy()` after iteration.
|
||||
* @param [out] jblp Pointer to be initialized by new object.
|
||||
*/
|
||||
IW_EXPORT iwrc jbl_create_iterator_holder(JBL *jblp);
|
||||
|
||||
/**
|
||||
* @brief Initialize allocated iterator over given `jbl` object.
|
||||
*
|
||||
* @param jbl JBL object to iterate
|
||||
* @param iter Iterator state placeholder allocated by `jbl_create_iter_placeholder()`
|
||||
*/
|
||||
IW_EXPORT iwrc jbl_iterator_init(JBL jbl, JBL_iterator *iter);
|
||||
|
||||
/**
|
||||
* @brief Get next value from JBL_iterator.
|
||||
* Returns `false` if iteration is over.
|
||||
*
|
||||
* @param iter Iterator object.
|
||||
* @param holder Holder to object pointed by current iteration.
|
||||
* @param pkey Key value holder. Zero in the case of iteration over array.
|
||||
* @param klen Key length or array index in the case of iteration over array.
|
||||
*/
|
||||
IW_EXPORT bool jbl_iterator_next(JBL_iterator *iter, JBL holder, char **pkey, int *klen);
|
||||
|
||||
//--- JBL_NODE
|
||||
|
||||
/**
|
||||
* @brief Converts `jbl` value to `JBL_NODE` tree.
|
||||
* @note `node` resources will be released when `pool` destroyed.
|
||||
*
|
||||
* @param jbl JSON document in compact `binn` format. Not zero.
|
||||
* @param [out] node Holder of new `JBL_NODE` value. Not zero.
|
||||
* @param clone_strings If `true` JSON keys and string values will be cloned into given `pool`
|
||||
* otherwise only pointers to strings will be assigned.
|
||||
* Use `true` if you want to be completely safe when given `jbl`
|
||||
* object will be destroyed.
|
||||
* @param pool Memory used to allocate new `JBL_NODE` tree. Not zero.
|
||||
*/
|
||||
IW_EXPORT iwrc jbl_to_node(JBL jbl, JBL_NODE *node, bool clone_strings, IWPOOL *pool);
|
||||
|
||||
/**
|
||||
* @brief Converts `json` text to `JBL_NODE` tree.
|
||||
* @note `node` resources will be released when `pool` destroyed.
|
||||
*
|
||||
* @param json JSON text
|
||||
* @param [out] node Holder of new `JBL_NODE` value. Not zero.
|
||||
* @param pool Memory used to allocate new `JBL_NODE` tree. Not zero.
|
||||
*/
|
||||
IW_EXPORT iwrc jbn_from_json(const char *json, JBL_NODE *node, IWPOOL *pool);
|
||||
|
||||
IW_EXPORT iwrc jbn_from_json_printf(JBL_NODE *node, IWPOOL *pool, const char *format, ...);
|
||||
|
||||
IW_EXPORT iwrc jbn_from_json_printf_va(JBL_NODE *node, IWPOOL *pool, const char *format, va_list va);
|
||||
|
||||
/**
|
||||
* @brief Prints JBL_NODE document as JSON string.
|
||||
*
|
||||
* @see jbl_fstream_json_printer()
|
||||
* @see jbl_xstr_json_printer()
|
||||
* @see jbl_count_json_printer()
|
||||
*
|
||||
* @param node `JBL_NODE` document. Not zero.
|
||||
* @param pt JSON printer function. Not zero.
|
||||
* @param op Pointer to user data for JSON printer function.
|
||||
* @param pf JSON printing mode.
|
||||
*/
|
||||
IW_EXPORT iwrc jbn_as_json(JBL_NODE node, jbl_json_printer pt, void *op, jbl_print_flags_t pf);
|
||||
|
||||
/**
|
||||
* @brief Fill `jbl` document by data from `node`.
|
||||
*
|
||||
* Common use case:
|
||||
* Create empty document with `jbl_create_empty_object()` `jbl_create_empty_array()`
|
||||
* then fill it with `jbl_fill_from_node()`
|
||||
*
|
||||
* @param jbl JBL document to be filled. Not zero.
|
||||
* @param node Source tree node. Not zero.
|
||||
*/
|
||||
IW_EXPORT iwrc jbl_fill_from_node(JBL jbl, JBL_NODE node);
|
||||
|
||||
/**
|
||||
* @brief Converts `node` object into JBL form.
|
||||
*
|
||||
* @param jblp JBL pointer holder. Not zero.
|
||||
* @param node Source tree node. Not zero.
|
||||
* @return IW_EXPORT jbl_from_node
|
||||
*/
|
||||
IW_EXPORT iwrc jbl_from_node(JBL *jblp, JBL_NODE node);
|
||||
|
||||
/**
|
||||
* @brief Compares JSON tree nodes.
|
||||
*
|
||||
* - Primitive JSON values compared as is.
|
||||
* - JSON arrays compared by values held in the same position in array.
|
||||
* - JSON objects compared by corresponding values held under lexicographically sorted keys.
|
||||
*
|
||||
* @param n1
|
||||
* @param n2
|
||||
* @param [out] rcp
|
||||
*
|
||||
* @return - Not zero if `n1` and `n2` have different types.
|
||||
* - Zero if `n1` and `n2` are equal.
|
||||
* - Greater than zero if `n1` greater than `n2`
|
||||
* - Lesser than zero if `n1` lesser than `n2`
|
||||
*/
|
||||
IW_EXPORT int jbn_compare_nodes(JBL_NODE n1, JBL_NODE n2, iwrc *rcp);
|
||||
|
||||
/**
|
||||
* @brief Add item to the `parent` container.
|
||||
*/
|
||||
IW_EXPORT void jbn_add_item(JBL_NODE parent, JBL_NODE node);
|
||||
|
||||
/**
|
||||
* @brief Adds string JSON node to the given `parent` node.
|
||||
* Key and value are copied into allocated node.
|
||||
*
|
||||
* @param parent Parent holder.
|
||||
* @param key Child node key cloned into node. Can be zero if parent is an array.
|
||||
* @param val Child node value copied.
|
||||
* @param vlen Langth of child node value.
|
||||
* If `vlen` is lesser then zero length of `val` will be determined my `strlen`.
|
||||
* @param node_out Optional placeholder for new node.
|
||||
* @param pool Allocation pool.
|
||||
*/
|
||||
IW_EXPORT iwrc jbn_add_item_str(
|
||||
JBL_NODE parent, const char *key, const char *val, int vlen, JBL_NODE *node_out,
|
||||
IWPOOL *pool);
|
||||
|
||||
/**
|
||||
* @brief Adds null JSON value to the given `parent` node.
|
||||
*
|
||||
* @param parent Parent holder.
|
||||
* @param key Child node key cloned into node. Can be zero if parent is an array.
|
||||
* @param pool Allocation pool.
|
||||
*/
|
||||
IW_EXPORT iwrc jbn_add_item_null(JBL_NODE parent, const char *key, IWPOOL *pool);
|
||||
|
||||
/**
|
||||
* @brief Adds integer JSON node to the given `parent` node.
|
||||
*
|
||||
* @param parent Parent holder.
|
||||
* @param key Child node key cloned into node. Can be zero if parent is an array.
|
||||
* @param val Integer value.
|
||||
* @param node_out Optional placeholder for new node.
|
||||
* @param pool Allocation pool.
|
||||
*/
|
||||
IW_EXPORT iwrc jbn_add_item_i64(JBL_NODE parent, const char *key, int64_t val, JBL_NODE *node_out, IWPOOL *pool);
|
||||
|
||||
/**
|
||||
* @brief Adds fp number JSON node to the given `parent` node.
|
||||
*
|
||||
* @param parent Parent holder.
|
||||
* @param key Child node key cloned into node. Can be zero if parent is an array.
|
||||
* @param val Floating point value.
|
||||
* @param node_out Optional placeholder for new node.
|
||||
* @param pool Allocation pool.
|
||||
*/
|
||||
IW_EXPORT iwrc jbn_add_item_f64(JBL_NODE parent, const char *key, double val, JBL_NODE *node_out, IWPOOL *pool);
|
||||
|
||||
/**
|
||||
* @brief Add nested object under the given `key`
|
||||
*
|
||||
* @param parent Parent holder
|
||||
* @param key Child node key cloned into node. Can be zero if parent is an array.
|
||||
* @param node_out [out] Pointer to new node, can be zero.
|
||||
* @param pool Allocation pool
|
||||
* @return IW_EXPORT jbn_add_item_obj
|
||||
*/
|
||||
IW_EXPORT iwrc jbn_add_item_obj(JBL_NODE parent, const char *key, JBL_NODE *node_out, IWPOOL *pool);
|
||||
|
||||
/**
|
||||
* @brief Add nested array under the given `key`
|
||||
*
|
||||
* @param parent Parent holder
|
||||
* @param key Child node key cloned into node. Can be zero if parent is an array.
|
||||
* @param node_out [out] Pointer to new node, can be zero.
|
||||
* @param pool Allocation pool
|
||||
* @return IW_EXPORT jbn_add_item_obj
|
||||
*/
|
||||
IW_EXPORT iwrc jbn_add_item_arr(JBL_NODE parent, const char *key, JBL_NODE *node_out, IWPOOL *pool);
|
||||
|
||||
/**
|
||||
* @brief Adds boolean JSON node to the given `parent` node.
|
||||
*
|
||||
* @param parent Parent holder.
|
||||
* @param key Child node key cloned into node. Can be zero if parent is an array.
|
||||
* @param val Boolean node value.
|
||||
* @param node_out [out] Pointer to new node, can be zero.
|
||||
* @param pool Allocation pool.
|
||||
*/
|
||||
IW_EXPORT iwrc jbn_add_item_bool(JBL_NODE parent, const char *key, bool val, JBL_NODE *node_out, IWPOOL *pool);
|
||||
|
||||
/**
|
||||
* @brief Add item from the `parent` container.
|
||||
*/
|
||||
IW_EXPORT void jbn_remove_item(JBL_NODE parent, JBL_NODE child);
|
||||
|
||||
/**
|
||||
* @brief Remove subtree from `target` node pointed by `path`
|
||||
*/
|
||||
IW_EXPORT JBL_NODE jbn_detach2(JBL_NODE target, JBL_PTR path);
|
||||
|
||||
IW_EXPORT JBL_NODE jbn_detach(JBL_NODE target, const char *path);
|
||||
|
||||
/**
|
||||
* @brief Reset tree `node` data.
|
||||
*/
|
||||
IW_EXPORT void jbn_data(JBL_NODE node);
|
||||
|
||||
/**
|
||||
* @brief Returns number of child elements of given node.
|
||||
*
|
||||
* @param node JBL node
|
||||
*/
|
||||
IW_EXPORT int jbn_length(JBL_NODE node);
|
||||
|
||||
/**
|
||||
* @brief Parses rfc6901 JSON path.
|
||||
* @note `jpp` structure should be disposed by `free()`.
|
||||
*
|
||||
* @param path JSON path string. Not zero.
|
||||
* @param [out] jpp Holder for parsed path structure. Not zero.
|
||||
*/
|
||||
IW_EXPORT iwrc jbl_ptr_alloc(const char *path, JBL_PTR *jpp);
|
||||
|
||||
/**
|
||||
* @brief Parses rfc6901 JSON path.
|
||||
*
|
||||
* @param path JSON path string. Not zero.
|
||||
* @param [out] jpp JSON path string. Not zero.
|
||||
* @param pool Pool used for memory allocation. Not zero.
|
||||
*/
|
||||
IW_EXPORT iwrc jbl_ptr_alloc_pool(const char *path, JBL_PTR *jpp, IWPOOL *pool);
|
||||
|
||||
/**
|
||||
* @brief Compare JSON pointers.
|
||||
*/
|
||||
IW_EXPORT int jbl_ptr_cmp(JBL_PTR p1, JBL_PTR p2);
|
||||
|
||||
/**
|
||||
* @brief Serialize JSON pointer to as text.
|
||||
* @param ptr JSON pointer. Not zero.
|
||||
* @param xstr Output string buffer. Not zero.
|
||||
*/
|
||||
IW_EXPORT iwrc jbl_ptr_serialize(JBL_PTR ptr, IWXSTR *xstr);
|
||||
|
||||
/**
|
||||
* @brief JBL_NODE visitor context
|
||||
*/
|
||||
typedef struct _JBN_VCTX {
|
||||
JBL_NODE root; /**< Root node from which started visitor */
|
||||
void *op; /**< Arbitrary opaque data */
|
||||
void *result;
|
||||
IWPOOL *pool; /**< Pool placeholder, initialization is responsibility of `JBN_VCTX` creator */
|
||||
int pos; /**< Aux position, not actually used by visitor core */
|
||||
bool terminate; /**< It `true` document traversal will be terminated immediately. */
|
||||
} JBN_VCTX;
|
||||
|
||||
/**
|
||||
* Call with lvl: `-1` means end of visiting whole object tree.
|
||||
*/
|
||||
typedef jbn_visitor_cmd_t (*JBN_VISITOR)(int lvl, JBL_NODE n, const char *key, int klidx, JBN_VCTX *vctx, iwrc *rc);
|
||||
|
||||
IW_EXPORT iwrc jbn_visit(JBL_NODE node, int lvl, JBN_VCTX *vctx, JBN_VISITOR visitor);
|
||||
|
||||
//--- PATCHING
|
||||
|
||||
IW_EXPORT iwrc jbn_patch_auto(JBL_NODE root, JBL_NODE patch, IWPOOL *pool);
|
||||
|
||||
IW_EXPORT iwrc jbn_merge_patch(JBL_NODE root, JBL_NODE patch, IWPOOL *pool);
|
||||
|
||||
IW_EXPORT iwrc jbn_patch(JBL_NODE root, const JBL_PATCH *patch, size_t cnt, IWPOOL *pool);
|
||||
|
||||
IW_EXPORT iwrc jbn_merge_patch_from_json(JBL_NODE root, const char *patchjson, IWPOOL *pool);
|
||||
|
||||
IW_EXPORT iwrc jbl_patch(JBL jbl, const JBL_PATCH *patch, size_t cnt);
|
||||
|
||||
IW_EXPORT iwrc jbl_patch_from_json(JBL jbl, const char *patchjson);
|
||||
|
||||
IW_EXPORT iwrc jbl_merge_patch(JBL jbl, const char *patchjson);
|
||||
|
||||
IW_EXPORT iwrc jbl_merge_patch_jbl(JBL jbl, JBL patch);
|
||||
|
||||
|
||||
IW_EXPORT iwrc jbl_init(void);
|
||||
|
||||
IW_EXTERN_C_END
|
||||
#endif
|
||||
@@ -0,0 +1,90 @@
|
||||
#pragma once
|
||||
#ifndef JBL_INTERNAL_H
|
||||
#define JBL_INTERNAL_H
|
||||
|
||||
/**************************************************************************************************
|
||||
* IOWOW library
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2012-2022 Softmotions Ltd <info@softmotions.com>
|
||||
*
|
||||
* 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.
|
||||
*************************************************************************************************/
|
||||
|
||||
#include "iwlog.h"
|
||||
#include "iwjson.h"
|
||||
#include "iwbinn.h"
|
||||
#include "iwpool.h"
|
||||
#include "iwconv.h"
|
||||
|
||||
#define JBL_MAX_NESTING_LEVEL 999
|
||||
|
||||
struct _JBL {
|
||||
binn bn;
|
||||
JBL_NODE node;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief JBL visitor context
|
||||
*/
|
||||
typedef struct _JBL_VCTX {
|
||||
binn *bn; /**< Root node from which started visitor */
|
||||
void *op; /**< Arbitrary opaque data */
|
||||
void *result;
|
||||
IWPOOL *pool; /**< Pool placeholder, initialization is responsibility of `JBL_VCTX` creator */
|
||||
int pos; /**< Aux position, not actually used by visitor core */
|
||||
bool terminate;
|
||||
bool found; /**< Used in _jbl_at() */
|
||||
} JBL_VCTX;
|
||||
|
||||
typedef jbn_visitor_cmd_t jbl_visitor_cmd_t;
|
||||
|
||||
typedef struct _JBL_PATCHEXT {
|
||||
const JBL_PATCH *p;
|
||||
JBL_PTR path;
|
||||
JBL_PTR from;
|
||||
} JBL_PATCHEXT;
|
||||
|
||||
typedef struct _JBLDRCTX {
|
||||
IWPOOL *pool;
|
||||
JBL_NODE root;
|
||||
} JBLDRCTX;
|
||||
|
||||
iwrc jbl_from_buf_keep_onstack(JBL jbl, void *buf, size_t bufsz);
|
||||
iwrc jbl_from_buf_keep_onstack2(JBL jbl, void *buf);
|
||||
|
||||
iwrc _jbl_write_double(double num, jbl_json_printer pt, void *op);
|
||||
iwrc _jbl_write_int(int64_t num, jbl_json_printer pt, void *op);
|
||||
iwrc _jbl_write_string(const char *str, int len, jbl_json_printer pt, void *op, jbl_print_flags_t pf);
|
||||
iwrc _jbl_node_from_binn(const binn *bn, JBL_NODE *node, bool clone_strings, IWPOOL *pool);
|
||||
iwrc _jbl_binn_from_node(binn *res, JBL_NODE node);
|
||||
iwrc _jbl_from_node(JBL jbl, JBL_NODE node);
|
||||
bool _jbl_at(JBL jbl, JBL_PTR jp, JBL res);
|
||||
int _jbl_compare_nodes(JBL_NODE n1, JBL_NODE n2, iwrc *rcp);
|
||||
|
||||
typedef jbl_visitor_cmd_t (*JBL_VISITOR)(int lvl, binn *bv, const char *key, int idx, JBL_VCTX *vctx, iwrc *rc);
|
||||
iwrc _jbl_visit(binn_iter *iter, int lvl, JBL_VCTX *vctx, JBL_VISITOR visitor);
|
||||
|
||||
bool _jbl_is_eq_atomic_values(JBL v1, JBL v2);
|
||||
int _jbl_cmp_atomic_values(JBL v1, JBL v2);
|
||||
|
||||
BOOL binn_read_next_pair2(int expected_type, binn_iter *iter, int *klidx, char **pkey, binn *value);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,20 @@
|
||||
link_libraries(iowow_s ${CUNIT_LIBRARIES})
|
||||
include_directories(${CUNIT_INCLUDE_DIRS})
|
||||
|
||||
file(GLOB datafiles RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/data" "data/*")
|
||||
foreach (file ${datafiles})
|
||||
configure_file("data/${file}" "data/${file}" COPYONLY)
|
||||
endforeach ()
|
||||
|
||||
set(TEST_DATA_DIR ${CMAKE_CURRENT_BINARY_DIR})
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${TEST_DATA_DIR})
|
||||
|
||||
foreach (TN IN ITEMS jbl_test1
|
||||
jbl_test_binn1
|
||||
jbl_test_binn2)
|
||||
add_executable(${TN} ${TN}.c)
|
||||
set_target_properties(${TN} PROPERTIES
|
||||
COMPILE_FLAGS "-DIW_STATIC")
|
||||
add_test(NAME ${TN} WORKING_DIRECTORY ${TEST_DATA_DIR}
|
||||
COMMAND ${TEST_TOOL_CMD} $<TARGET_FILE:${TN}>)
|
||||
endforeach ()
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"str": "𝌆",
|
||||
"str1": "15øC 3đ",
|
||||
"str2": "Mа二𐌂",
|
||||
"str3": "привет",
|
||||
"na": [
|
||||
0.1,
|
||||
0.000006,
|
||||
0.000001
|
||||
],
|
||||
"glossary": {
|
||||
"title": "example glossary",
|
||||
"GlossDiv": {
|
||||
"title": "S",
|
||||
"GlossList": {
|
||||
"GlossEntry": {
|
||||
"ID": "SGML",
|
||||
"SortAs": "SGML",
|
||||
"GlossTerm": "Standard Generalized Markup Language",
|
||||
"Acronym": "SGML",
|
||||
"Abbrev": "ISO 8879:1986",
|
||||
"GlossDef": {
|
||||
"para": "A meta-markup language, used to create markup languages such as DocBook.",
|
||||
"GlossSeeAlso": [
|
||||
"GML",
|
||||
"XML",
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"GlossSee": "markup"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"str": "\uD834\uDF06",
|
||||
"str1": "15\u00f8C 3\u0111",
|
||||
"str2": "\u004d\u0430\u4e8c\ud800\udf02",
|
||||
"str3": "привет",
|
||||
"na": [
|
||||
0.1000,
|
||||
6E-06,
|
||||
1E-06
|
||||
],
|
||||
"glossary": {
|
||||
"title": "example glossary",
|
||||
"GlossDiv": {
|
||||
"title": "S",
|
||||
"GlossList": {
|
||||
"GlossEntry": {
|
||||
"ID": "SGML",
|
||||
"SortAs": "SGML",
|
||||
"GlossTerm": "Standard Generalized Markup Language",
|
||||
"Acronym": "SGML",
|
||||
"Abbrev": "ISO 8879:1986",
|
||||
"GlossDef": {
|
||||
"para": "A meta-markup language, used to create markup languages such as DocBook.",
|
||||
"GlossSeeAlso": [
|
||||
"GML",
|
||||
"XML",
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"GlossSee": "markup"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"str": "\uD834\uDF06",
|
||||
"str1": "15\u00F8C 3\u0111",
|
||||
"str2": "M\u0430\u4E8C\uD800\uDF02",
|
||||
"str3": "\u043F\u0440\u0438\u0432\u0435\u0442",
|
||||
"na": [
|
||||
0.00012,
|
||||
0.000006,
|
||||
0.000001
|
||||
],
|
||||
"glossary": {
|
||||
"title": "example glossary",
|
||||
"GlossDiv": {
|
||||
"title": "S",
|
||||
"GlossList": {
|
||||
"GlossEntry": {
|
||||
"ID": "SGML",
|
||||
"SortAs": "SGML",
|
||||
"GlossTerm": "Standard Generalized Markup Language",
|
||||
"Acronym": "SGML",
|
||||
"Abbrev": "ISO 8879:1986",
|
||||
"GlossDef": {
|
||||
"para": "A meta-markup language, used to create markup languages such as DocBook.",
|
||||
"GlossSeeAlso": [
|
||||
"GML",
|
||||
"XML",
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"GlossSee": "markup"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"str": "\uD834\uDF06",
|
||||
"str1": "15\u00f8C 3\u0111",
|
||||
"str2": "\u004d\u0430\u4e8c\ud800\udf02",
|
||||
"str3": "привет",
|
||||
"na": [
|
||||
0.00011999999999999999,
|
||||
6E-06,
|
||||
1E-06
|
||||
],
|
||||
"glossary": {
|
||||
"title": "example glossary",
|
||||
"GlossDiv": {
|
||||
"title": "S",
|
||||
"GlossList": {
|
||||
"GlossEntry": {
|
||||
"ID": "SGML",
|
||||
"SortAs": "SGML",
|
||||
"GlossTerm": "Standard Generalized Markup Language",
|
||||
"Acronym": "SGML",
|
||||
"Abbrev": "ISO 8879:1986",
|
||||
"GlossDef": {
|
||||
"para": "A meta-markup language, used to create markup languages such as DocBook.",
|
||||
"GlossSeeAlso": [
|
||||
"GML",
|
||||
"XML",
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"GlossSee": "markup"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"empty": ""
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"empty": ""
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"web-app": {
|
||||
"servlet": [
|
||||
{
|
||||
"servlet-name": "cofaxCDS",
|
||||
"servlet-class": "org.cofax.cds.CDSServlet",
|
||||
"init-param": {
|
||||
"configGlossary:installationAt": "Philadelphia, PA",
|
||||
"configGlossary:adminEmail": "ksm@pobox.com",
|
||||
"configGlossary:poweredBy": "Cofax",
|
||||
"configGlossary:poweredByIcon": "/images/cofax.gif",
|
||||
"configGlossary:staticPath": "/content/static",
|
||||
"templateProcessorClass": "org.cofax.WysiwygTemplate",
|
||||
"templateLoaderClass": "org.cofax.FilesTemplateLoader",
|
||||
"templatePath": "templates",
|
||||
"templateOverridePath": "",
|
||||
"defaultListTemplate": "listTemplate.htm",
|
||||
"defaultFileTemplate": "articleTemplate.htm",
|
||||
"useJSP": false,
|
||||
"jspListTemplate": "listTemplate.jsp",
|
||||
"jspFileTemplate": "articleTemplate.jsp",
|
||||
"cachePackageTagsTrack": 200,
|
||||
"cachePackageTagsStore": 200,
|
||||
"cachePackageTagsRefresh": 60,
|
||||
"cacheTemplatesTrack": 100,
|
||||
"cacheTemplatesStore": 50,
|
||||
"cacheTemplatesRefresh": 15,
|
||||
"cachePagesTrack": 200,
|
||||
"cachePagesStore": 100,
|
||||
"cachePagesRefresh": 10,
|
||||
"cachePagesDirtyRead": 10,
|
||||
"searchEngineListTemplate": "forSearchEnginesList.htm",
|
||||
"searchEngineFileTemplate": "forSearchEngines.htm",
|
||||
"searchEngineRobotsDb": "WEB-INF/robots.db",
|
||||
"useDataStore": true,
|
||||
"dataStoreClass": "org.cofax.SqlDataStore",
|
||||
"redirectionClass": "org.cofax.SqlRedirection",
|
||||
"dataStoreName": "cofax",
|
||||
"dataStoreDriver": "com.microsoft.jdbc.sqlserver.SQLServerDriver",
|
||||
"dataStoreUrl": "jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon",
|
||||
"dataStoreUser": "sa",
|
||||
"dataStorePassword": "dataStoreTestQuery",
|
||||
"dataStoreTestQuery": "SET NOCOUNT ON;select test='test';",
|
||||
"dataStoreLogFile": "/usr/local/tomcat/logs/datastore.log",
|
||||
"dataStoreInitConns": 10,
|
||||
"dataStoreMaxConns": 100,
|
||||
"dataStoreConnUsageLimit": 100,
|
||||
"dataStoreLogLevel": "debug",
|
||||
"maxUrlLength": 500
|
||||
}
|
||||
},
|
||||
{
|
||||
"servlet-name": "cofaxEmail",
|
||||
"servlet-class": "org.cofax.cds.EmailServlet",
|
||||
"init-param": {
|
||||
"mailHost": "mail1",
|
||||
"mailHostOverride": "mail2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"servlet-name": "cofaxAdmin",
|
||||
"servlet-class": "org.cofax.cds.AdminServlet"
|
||||
},
|
||||
{
|
||||
"servlet-name": "fileServlet",
|
||||
"servlet-class": "org.cofax.cds.FileServlet"
|
||||
},
|
||||
{
|
||||
"servlet-name": "cofaxTools",
|
||||
"servlet-class": "org.cofax.cms.CofaxToolsServlet",
|
||||
"init-param": {
|
||||
"templatePath": "toolstemplates/",
|
||||
"log": 1,
|
||||
"logLocation": "/usr/local/tomcat/logs/CofaxTools.log",
|
||||
"logMaxSize": "",
|
||||
"dataLog": 1,
|
||||
"dataLogLocation": "/usr/local/tomcat/logs/dataLog.log",
|
||||
"dataLogMaxSize": "",
|
||||
"removePageCache": "/content/admin/remove?cache=pages&id=",
|
||||
"removeTemplateCache": "/content/admin/remove?cache=templates&id=",
|
||||
"fileTransferFolder": "/usr/local/tomcat/webapps/content/fileTransferFolder",
|
||||
"lookInContext": 1,
|
||||
"adminGroupID": 4,
|
||||
"betaServer": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"servlet-mapping": {
|
||||
"cofaxCDS": "/",
|
||||
"cofaxEmail": "/cofaxutil/aemail/*",
|
||||
"cofaxAdmin": "/admin/*",
|
||||
"fileServlet": "/static/*",
|
||||
"cofaxTools": "/tools/*"
|
||||
},
|
||||
"taglib": {
|
||||
"taglib-uri": "cofax.tld",
|
||||
"taglib-location": "/WEB-INF/tlds/cofax.tld"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"web-app": {
|
||||
"servlet": [
|
||||
{
|
||||
"servlet-name": "cofaxCDS",
|
||||
"servlet-class": "org.cofax.cds.CDSServlet",
|
||||
"init-param": {
|
||||
"configGlossary:installationAt": "Philadelphia, PA",
|
||||
"configGlossary:adminEmail": "ksm@pobox.com",
|
||||
"configGlossary:poweredBy": "Cofax",
|
||||
"configGlossary:poweredByIcon": "/images/cofax.gif",
|
||||
"configGlossary:staticPath": "/content/static",
|
||||
"templateProcessorClass": "org.cofax.WysiwygTemplate",
|
||||
"templateLoaderClass": "org.cofax.FilesTemplateLoader",
|
||||
"templatePath": "templates",
|
||||
"templateOverridePath": "",
|
||||
"defaultListTemplate": "listTemplate.htm",
|
||||
"defaultFileTemplate": "articleTemplate.htm",
|
||||
"useJSP": false,
|
||||
"jspListTemplate": "listTemplate.jsp",
|
||||
"jspFileTemplate": "articleTemplate.jsp",
|
||||
"cachePackageTagsTrack": 200,
|
||||
"cachePackageTagsStore": 200,
|
||||
"cachePackageTagsRefresh": 60,
|
||||
"cacheTemplatesTrack": 100,
|
||||
"cacheTemplatesStore": 50,
|
||||
"cacheTemplatesRefresh": 15,
|
||||
"cachePagesTrack": 200,
|
||||
"cachePagesStore": 100,
|
||||
"cachePagesRefresh": 10,
|
||||
"cachePagesDirtyRead": 10,
|
||||
"searchEngineListTemplate": "forSearchEnginesList.htm",
|
||||
"searchEngineFileTemplate": "forSearchEngines.htm",
|
||||
"searchEngineRobotsDb": "WEB-INF/robots.db",
|
||||
"useDataStore": true,
|
||||
"dataStoreClass": "org.cofax.SqlDataStore",
|
||||
"redirectionClass": "org.cofax.SqlRedirection",
|
||||
"dataStoreName": "cofax",
|
||||
"dataStoreDriver": "com.microsoft.jdbc.sqlserver.SQLServerDriver",
|
||||
"dataStoreUrl": "jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon",
|
||||
"dataStoreUser": "sa",
|
||||
"dataStorePassword": "dataStoreTestQuery",
|
||||
"dataStoreTestQuery": "SET NOCOUNT ON;select test='test';",
|
||||
"dataStoreLogFile": "/usr/local/tomcat/logs/datastore.log",
|
||||
"dataStoreInitConns": 10,
|
||||
"dataStoreMaxConns": 100,
|
||||
"dataStoreConnUsageLimit": 100,
|
||||
"dataStoreLogLevel": "debug",
|
||||
"maxUrlLength": 500
|
||||
}
|
||||
},
|
||||
{
|
||||
"servlet-name": "cofaxEmail",
|
||||
"servlet-class": "org.cofax.cds.EmailServlet",
|
||||
"init-param": {
|
||||
"mailHost": "mail1",
|
||||
"mailHostOverride": "mail2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"servlet-name": "cofaxAdmin",
|
||||
"servlet-class": "org.cofax.cds.AdminServlet"
|
||||
},
|
||||
{
|
||||
"servlet-name": "fileServlet",
|
||||
"servlet-class": "org.cofax.cds.FileServlet"
|
||||
},
|
||||
{
|
||||
"servlet-name": "cofaxTools",
|
||||
"servlet-class": "org.cofax.cms.CofaxToolsServlet",
|
||||
"init-param": {
|
||||
"templatePath": "toolstemplates/",
|
||||
"log": 1,
|
||||
"logLocation": "/usr/local/tomcat/logs/CofaxTools.log",
|
||||
"logMaxSize": "",
|
||||
"dataLog": 1,
|
||||
"dataLogLocation": "/usr/local/tomcat/logs/dataLog.log",
|
||||
"dataLogMaxSize": "",
|
||||
"removePageCache": "/content/admin/remove?cache=pages&id=",
|
||||
"removeTemplateCache": "/content/admin/remove?cache=templates&id=",
|
||||
"fileTransferFolder": "/usr/local/tomcat/webapps/content/fileTransferFolder",
|
||||
"lookInContext": 1,
|
||||
"adminGroupID": 4,
|
||||
"betaServer": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"servlet-mapping": {
|
||||
"cofaxCDS": "/",
|
||||
"cofaxEmail": "/cofaxutil/aemail/*",
|
||||
"cofaxAdmin": "/admin/*",
|
||||
"fileServlet": "/static/*",
|
||||
"cofaxTools": "/tools/*"
|
||||
},
|
||||
"taglib": {
|
||||
"taglib-uri": "cofax.tld",
|
||||
"taglib-location": "/WEB-INF/tlds/cofax.tld"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"foo": "b\"ar",
|
||||
"num1": 1223,
|
||||
"n\"um2": 10.1226222,
|
||||
"list": [
|
||||
3,
|
||||
2.1,
|
||||
1,
|
||||
"one",
|
||||
"two",
|
||||
{},
|
||||
{
|
||||
"z": false,
|
||||
"t": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"foo": "b\"ar", "num1":1223,"n\"um2":10.1226222, "list":[3,2.1,1,"one", "two", {}, {"z":false, "t":true}]}
|
||||
@@ -0,0 +1,906 @@
|
||||
#include "iwxstr.h"
|
||||
#include "iwutils.h"
|
||||
|
||||
#include "iwjson.h"
|
||||
#include "iwjson_internal.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <CUnit/Basic.h>
|
||||
|
||||
int init_suite(void) {
|
||||
int rc = iw_init();
|
||||
return rc;
|
||||
}
|
||||
|
||||
int clean_suite(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void _jbl_test1_1(int num, iwrc expected, jbl_print_flags_t pf) {
|
||||
iwrc rc;
|
||||
char path[64];
|
||||
char path_expected[64];
|
||||
JBL_NODE node = 0;
|
||||
IWPOOL *pool;
|
||||
char *data;
|
||||
char *edata = 0;
|
||||
IWXSTR *res = iwxstr_new();
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(res);
|
||||
|
||||
snprintf(path, sizeof(path), "data%c%03d.json", IW_PATH_CHR, num);
|
||||
snprintf(path_expected, sizeof(path_expected), "data%c%03d.expected.json", IW_PATH_CHR, num);
|
||||
data = iwu_file_read_as_buf(path);
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(data);
|
||||
|
||||
pool = iwpool_create(1024);
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(pool);
|
||||
|
||||
rc = jbn_from_json(data, &node, pool);
|
||||
if (rc) {
|
||||
iwlog_ecode_error3(rc);
|
||||
}
|
||||
CU_ASSERT_EQUAL_FATAL(rc, expected);
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(node);
|
||||
if (expected) {
|
||||
goto finish;
|
||||
}
|
||||
|
||||
rc = jbn_as_json(node, jbl_xstr_json_printer, res, pf);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
|
||||
edata = iwu_file_read_as_buf(path_expected);
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(edata);
|
||||
|
||||
FILE *f1 = fopen("f1.txt", "w");
|
||||
FILE *f2 = fopen("f2.txt", "w");
|
||||
fprintf(f1, "\n%s", edata);
|
||||
fprintf(f2, "\n%s", iwxstr_ptr(res));
|
||||
fclose(f1);
|
||||
fclose(f2);
|
||||
|
||||
fprintf(stderr, "ED %s\n", edata);
|
||||
fprintf(stderr, "ED %s\n", iwxstr_ptr(res));
|
||||
|
||||
CU_ASSERT_EQUAL_FATAL(strcmp(edata, iwxstr_ptr(res)), 0);
|
||||
|
||||
finish:
|
||||
if (edata) {
|
||||
free(edata);
|
||||
}
|
||||
free(data);
|
||||
iwpool_destroy(pool);
|
||||
iwxstr_destroy(res);
|
||||
}
|
||||
|
||||
void jbl_test1_1() {
|
||||
_jbl_test1_1(1, 0, JBL_PRINT_PRETTY);
|
||||
_jbl_test1_1(2, 0, JBL_PRINT_PRETTY | JBL_PRINT_CODEPOINTS);
|
||||
_jbl_test1_1(3, 0, JBL_PRINT_PRETTY);
|
||||
_jbl_test1_1(4, 0, JBL_PRINT_PRETTY);
|
||||
_jbl_test1_1(5, 0, JBL_PRINT_PRETTY);
|
||||
}
|
||||
|
||||
void jbl_test1_2() {
|
||||
const char *data = "{\"foo\": \"b\\\"ar\", \"num1\":1223,"
|
||||
"\"n\\\"um2\":10.1226222, "
|
||||
"\"list\":[3,2.1,1,\"one\", \"two\", "
|
||||
"{}, {\"z\":false, \"t\":true}]}";
|
||||
JBL jbl;
|
||||
iwrc rc = jbl_from_json(&jbl, data);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
|
||||
IWXSTR *xstr = iwxstr_new();
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(xstr);
|
||||
|
||||
rc = jbl_as_json(jbl, jbl_xstr_json_printer, xstr, false);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
|
||||
int res = strcmp(iwxstr_ptr(xstr),
|
||||
"{\"foo\":\"b\\\"ar\",\"num1\":1223,\"n\\\"um2\":10.1226222,"
|
||||
"\"list\":[3,2.1,1,\"one\",\"two\",{},{\"z\":false,\"t\":true}]}");
|
||||
CU_ASSERT_EQUAL(res, 0);
|
||||
jbl_destroy(&jbl);
|
||||
|
||||
//
|
||||
rc = jbl_from_json(&jbl, "{ ");
|
||||
CU_ASSERT_EQUAL_FATAL(rc, JBL_ERROR_PARSE_JSON);
|
||||
|
||||
iwxstr_destroy(xstr);
|
||||
}
|
||||
|
||||
void jbl_test1_3() {
|
||||
JBL_PTR jp;
|
||||
iwrc rc = jbl_ptr_alloc("/", &jp);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
CU_ASSERT_EQUAL(jp->cnt, 1);
|
||||
CU_ASSERT_TRUE(*jp->n[0] == '\0')
|
||||
free(jp);
|
||||
|
||||
rc = jbl_ptr_alloc("/foo", &jp);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
CU_ASSERT_EQUAL(jp->cnt, 1);
|
||||
CU_ASSERT_FALSE(strcmp(jp->n[0], "foo"));
|
||||
free(jp);
|
||||
|
||||
rc = jbl_ptr_alloc("/foo/bar", &jp);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
CU_ASSERT_EQUAL(jp->cnt, 2);
|
||||
CU_ASSERT_FALSE(strcmp(jp->n[0], "foo"));
|
||||
CU_ASSERT_FALSE(strcmp(jp->n[1], "bar"));
|
||||
free(jp);
|
||||
|
||||
rc = jbl_ptr_alloc("/foo/bar/0/baz", &jp);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
CU_ASSERT_EQUAL(jp->cnt, 4);
|
||||
CU_ASSERT_FALSE(strcmp(jp->n[0], "foo"));
|
||||
CU_ASSERT_FALSE(strcmp(jp->n[1], "bar"));
|
||||
CU_ASSERT_FALSE(strcmp(jp->n[2], "0"));
|
||||
CU_ASSERT_FALSE(strcmp(jp->n[3], "baz"));
|
||||
free(jp);
|
||||
|
||||
rc = jbl_ptr_alloc("/foo/b~0ar/0/b~1az", &jp);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
CU_ASSERT_EQUAL(jp->cnt, 4);
|
||||
CU_ASSERT_FALSE(strcmp(jp->n[0], "foo"));
|
||||
CU_ASSERT_FALSE(strcmp(jp->n[1], "b~ar"));
|
||||
CU_ASSERT_FALSE(strcmp(jp->n[2], "0"));
|
||||
CU_ASSERT_FALSE(strcmp(jp->n[3], "b/az"));
|
||||
free(jp);
|
||||
|
||||
rc = jbl_ptr_alloc("/foo/", &jp);
|
||||
CU_ASSERT_EQUAL(rc, JBL_ERROR_JSON_POINTER);
|
||||
free(jp);
|
||||
|
||||
rc = jbl_ptr_alloc("//", &jp);
|
||||
CU_ASSERT_EQUAL(rc, JBL_ERROR_JSON_POINTER);
|
||||
free(jp);
|
||||
|
||||
rc = jbl_ptr_alloc("", &jp);
|
||||
CU_ASSERT_EQUAL(rc, JBL_ERROR_JSON_POINTER);
|
||||
free(jp);
|
||||
|
||||
rc = jbl_ptr_alloc("~", &jp);
|
||||
CU_ASSERT_EQUAL(rc, JBL_ERROR_JSON_POINTER);
|
||||
free(jp);
|
||||
}
|
||||
|
||||
void jbl_test1_4() {
|
||||
// { "foo": "bar",
|
||||
// "foo2": {
|
||||
// "foo3": {
|
||||
// "foo4": "bar4"
|
||||
// },
|
||||
// "foo5": "bar5"
|
||||
// },
|
||||
// "num1": 1,
|
||||
// "list1": ["one", "two", {"three": 3}]
|
||||
// }
|
||||
char *data
|
||||
= iwu_replace_char(
|
||||
strdup("{'foo':'bar','foo2':{'foo3':{'foo4':'bar4'},'foo5':'bar5'},"
|
||||
"'num1':1,'list1':['one','two',{'three':3}]}"),
|
||||
'\'', '"');
|
||||
JBL jbl, at, at2;
|
||||
const char *sval;
|
||||
int ival;
|
||||
iwrc rc = jbl_from_json(&jbl, data);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
|
||||
rc = jbl_at(jbl, "/foo", &at);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(at);
|
||||
|
||||
sval = jbl_get_str(at);
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(sval);
|
||||
CU_ASSERT_STRING_EQUAL(sval, "bar");
|
||||
jbl_destroy(&at);
|
||||
|
||||
rc = jbl_at(jbl, "/foo2/foo3", &at);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(at);
|
||||
CU_ASSERT_TRUE(at->bn.type == BINN_OBJECT);
|
||||
rc = jbl_at(at, "/foo4", &at2);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(at2);
|
||||
sval = jbl_get_str(at2);
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(sval);
|
||||
CU_ASSERT_STRING_EQUAL(sval, "bar4");
|
||||
jbl_destroy(&at2);
|
||||
jbl_destroy(&at);
|
||||
|
||||
at = (void*) 1;
|
||||
rc = jbl_at(jbl, "/foo2/foo10", &at);
|
||||
CU_ASSERT_EQUAL(rc, JBL_ERROR_PATH_NOTFOUND);
|
||||
CU_ASSERT_PTR_NULL(at);
|
||||
rc = 0;
|
||||
|
||||
rc = jbl_at(jbl, "/foo2/*/foo4", &at);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(at);
|
||||
sval = jbl_get_str(at);
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(sval);
|
||||
CU_ASSERT_STRING_EQUAL(sval, "bar4");
|
||||
jbl_destroy(&at);
|
||||
|
||||
rc = jbl_at(jbl, "/list1/1", &at);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(at);
|
||||
sval = jbl_get_str(at);
|
||||
CU_ASSERT_STRING_EQUAL(sval, "two");
|
||||
jbl_destroy(&at);
|
||||
|
||||
rc = jbl_at(jbl, "/list1/2/three", &at);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(at);
|
||||
ival = jbl_get_i32(at);
|
||||
CU_ASSERT_EQUAL(ival, 3);
|
||||
jbl_destroy(&at);
|
||||
|
||||
rc = jbl_at(jbl, "/list1/*/three", &at);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(at);
|
||||
ival = jbl_get_i32(at);
|
||||
CU_ASSERT_EQUAL(ival, 3);
|
||||
jbl_destroy(&at);
|
||||
|
||||
rc = jbl_at(jbl, "/list1/*/*", &at);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(at);
|
||||
ival = jbl_get_i32(at);
|
||||
CU_ASSERT_EQUAL(ival, 3);
|
||||
jbl_destroy(&at);
|
||||
|
||||
jbl_destroy(&jbl);
|
||||
free(data);
|
||||
}
|
||||
|
||||
void jbl_test1_5() {
|
||||
IWXSTR *xstr = iwxstr_new();
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(xstr);
|
||||
|
||||
// { "foo": "bar",
|
||||
// "foo2": {
|
||||
// "foo3": {
|
||||
// "foo4": "bar4"
|
||||
// },
|
||||
// "foo5": "bar5"
|
||||
// },
|
||||
// "num1": 1,
|
||||
// "list1": ["one", "two", {"three": 3}]
|
||||
// }
|
||||
char *data
|
||||
= iwu_replace_char(
|
||||
strdup("{'foo':'bar','foo2':{'foo3':{'foo4':'bar4'},'foo5':'bar5'},"
|
||||
"'num1':1,'list1':['one','two',{'three':3}]}"),
|
||||
'\'', '"');
|
||||
JBL jbl;
|
||||
int res = 0;
|
||||
|
||||
// Remove ROOT
|
||||
JBL_PATCH p1[] = { { .op = JBP_REMOVE, .path = "/" } };
|
||||
iwrc rc = jbl_from_json(&jbl, data);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
rc = jbl_patch(jbl, p1, sizeof(p1) / sizeof(p1[0]));
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
jbl_destroy(&jbl);
|
||||
|
||||
|
||||
// Remove "/foo"
|
||||
JBL_PATCH p2[] = { { .op = JBP_REMOVE, .path = "/foo" } };
|
||||
rc = jbl_from_json(&jbl, data);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
rc = jbl_patch(jbl, p2, sizeof(p2) / sizeof(p2[0]));
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
rc = jbl_as_json(jbl, jbl_xstr_json_printer, xstr, false);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
res = strcmp(iwxstr_ptr(
|
||||
xstr),
|
||||
"{\"foo2\":{\"foo3\":{\"foo4\":\"bar4\"},\"foo5\":\"bar5\"},\"num1\":1,\"list1\":[\"one\",\"two\",{\"three\":3}]}");
|
||||
CU_ASSERT_EQUAL(res, 0);
|
||||
jbl_destroy(&jbl);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
// Remove /foo2/foo3/foo4
|
||||
// Remove /list1/1
|
||||
JBL_PATCH p3[] = {
|
||||
{ .op = JBP_REMOVE, .path = "/foo2/foo3/foo4" },
|
||||
{ .op = JBP_REMOVE, .path = "/list1/1" }
|
||||
};
|
||||
rc = jbl_from_json(&jbl, data);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
rc = jbl_patch(jbl, p3, sizeof(p3) / sizeof(p3[0]));
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
rc = jbl_as_json(jbl, jbl_xstr_json_printer, xstr, false);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
res = strcmp(iwxstr_ptr(xstr),
|
||||
"{\"foo\":\"bar\",\"foo2\":{\"foo3\":{},\"foo5\":\"bar5\"},\"num1\":1,\"list1\":[\"one\",{\"three\":3}]}");
|
||||
CU_ASSERT_EQUAL(res, 0);
|
||||
jbl_destroy(&jbl);
|
||||
iwxstr_clear(xstr);
|
||||
iwxstr_destroy(xstr);
|
||||
free(data);
|
||||
}
|
||||
|
||||
void apply_patch(const char *data, const char *patch, const char *result, IWXSTR *xstr, iwrc *rcp) {
|
||||
CU_ASSERT_TRUE_FATAL(data && patch && xstr && rcp);
|
||||
JBL jbl = 0;
|
||||
char *data2 = iwu_replace_char(strdup(data), '\'', '"');
|
||||
char *patch2 = iwu_replace_char(strdup(patch), '\'', '"');
|
||||
char *result2 = result ? iwu_replace_char(strdup(result), '\'', '"') : 0;
|
||||
CU_ASSERT_TRUE_FATAL(data2 && patch2);
|
||||
|
||||
iwrc rc = jbl_from_json(&jbl, data2);
|
||||
RCGO(rc, finish);
|
||||
|
||||
rc = jbl_patch_from_json(jbl, patch2);
|
||||
RCGO(rc, finish);
|
||||
|
||||
rc = jbl_as_json(jbl, jbl_xstr_json_printer, xstr, false);
|
||||
RCGO(rc, finish);
|
||||
|
||||
if (result2) {
|
||||
CU_ASSERT_STRING_EQUAL(result2, iwxstr_ptr(xstr));
|
||||
}
|
||||
|
||||
finish:
|
||||
if (data2) {
|
||||
free(data2);
|
||||
}
|
||||
if (patch2) {
|
||||
free(patch2);
|
||||
}
|
||||
if (result2) {
|
||||
free(result2);
|
||||
}
|
||||
if (jbl) {
|
||||
jbl_destroy(&jbl);
|
||||
}
|
||||
*rcp = rc;
|
||||
}
|
||||
|
||||
void apply_merge_patch(const char *data, const char *patch, const char *result, IWXSTR *xstr, iwrc *rcp) {
|
||||
CU_ASSERT_TRUE_FATAL(data && patch && xstr && rcp);
|
||||
JBL jbl = 0;
|
||||
char *data2 = iwu_replace_char(strdup(data), '\'', '"');
|
||||
char *patch2 = iwu_replace_char(strdup(patch), '\'', '"');
|
||||
char *result2 = result ? iwu_replace_char(strdup(result), '\'', '"') : 0;
|
||||
CU_ASSERT_TRUE_FATAL(data2 && patch2);
|
||||
|
||||
iwrc rc = jbl_from_json(&jbl, data2);
|
||||
RCGO(rc, finish);
|
||||
|
||||
rc = jbl_merge_patch(jbl, patch2);
|
||||
RCGO(rc, finish);
|
||||
|
||||
rc = jbl_as_json(jbl, jbl_xstr_json_printer, xstr, false);
|
||||
RCGO(rc, finish);
|
||||
|
||||
if (result2) {
|
||||
CU_ASSERT_STRING_EQUAL(result2, iwxstr_ptr(xstr));
|
||||
}
|
||||
|
||||
finish:
|
||||
if (data2) {
|
||||
free(data2);
|
||||
}
|
||||
if (patch2) {
|
||||
free(patch2);
|
||||
}
|
||||
if (result2) {
|
||||
free(result2);
|
||||
}
|
||||
if (jbl) {
|
||||
jbl_destroy(&jbl);
|
||||
}
|
||||
*rcp = rc;
|
||||
}
|
||||
|
||||
// Run tests: https://github.com/json-patch/json-patch-tests/blob/master/spec_tests.json
|
||||
void jbl_test1_6() {
|
||||
iwrc rc;
|
||||
IWXSTR *xstr = iwxstr_new();
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(xstr);
|
||||
|
||||
apply_patch("{'foo':'bar','foo2':{'foo3':{'foo4':'bar4'},'foo5':'bar5'},'num1':1,'list1':['one','two',{'three':3}]}",
|
||||
"[{'op':'remove', 'path':'/foo'}]",
|
||||
"{'foo2':{'foo3':{'foo4':'bar4'},'foo5':'bar5'},'num1':1,'list1':['one','two',{'three':3}]}",
|
||||
xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
// 4.1. add with missing object
|
||||
apply_patch("{ 'q': { 'bar': 2 } }",
|
||||
"[ {'op': 'add', 'path': '/a/b', 'value': 1} ]",
|
||||
0, xstr, &rc);
|
||||
CU_ASSERT_EQUAL(rc, JBL_ERROR_PATCH_TARGET_INVALID);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
// A.1. Adding an Object Member
|
||||
apply_patch("{'foo': 'bar'}",
|
||||
"[ { 'op': 'add', 'path': '/baz', 'value': 'qux' } ]",
|
||||
"{'foo':'bar','baz':'qux'}", xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
// A.2. Adding an Array Element
|
||||
apply_patch("{'foo': [ 'bar', 'baz' ]}",
|
||||
"[{ 'op': 'add', 'path': '/foo/1', 'value': 'qux' }]",
|
||||
"{'foo':['bar','qux','baz']}", xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
// A.3. Removing an Object Member
|
||||
apply_patch("{'baz': 'qux','foo': 'bar'}",
|
||||
"[{ 'op': 'remove', 'path': '/baz' }]",
|
||||
"{'foo':'bar'}", xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
// A.4. Removing an Array Element
|
||||
apply_patch("{'foo': [ 'bar', 'qux', 'baz' ]}",
|
||||
"[{ 'op': 'remove', 'path': '/foo/1' }]",
|
||||
"{'foo':['bar','baz']}", xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
// A.5. Replacing a Value
|
||||
apply_patch("{'baz': 'qux','foo': 'bar'}",
|
||||
"[{ 'op': 'replace', 'path': '/baz', 'value': 'boo' }]",
|
||||
"{'foo':'bar','baz':'boo'}", xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
// A.5.1 #232
|
||||
apply_patch("{'a':{'c':'N','s':'F'}}",
|
||||
"[{'op':'replace', 'path':'/a/s', 'value':'A'}]",
|
||||
"{'a':{'c':'N','s':'A'}}", xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
// A.6. Moving a Value
|
||||
apply_patch("{'foo': {'bar': 'baz','waldo': 'fred'},'qux': {'corge': 'grault'}}",
|
||||
"[{ 'op': 'move', 'from': '/foo/waldo', 'path': '/qux/thud' }]",
|
||||
"{'foo':{'bar':'baz'},'qux':{'corge':'grault','thud':'fred'}}", xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
// A.7. Moving an Array Element
|
||||
apply_patch("{'foo': [ 'all', 'grass', 'cows', 'eat' ]}",
|
||||
"[{ 'op': 'move', 'from': '/foo/1', 'path': '/foo/3' }]",
|
||||
"{'foo':['all','cows','eat','grass']}", xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
// A.8. Testing a Value: Success
|
||||
apply_patch("{'baz': 'qux','foo': [ 'a', 2, 'c' ]}",
|
||||
"["
|
||||
"{ 'op': 'test', 'path': '/baz', 'value': 'qux' },"
|
||||
"{ 'op': 'test', 'path': '/foo/1', 'value': 2 }"
|
||||
"]",
|
||||
"{'baz':'qux','foo':['a',2,'c']}", xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
// A.8. Testing a Value Object
|
||||
apply_patch(
|
||||
"{'foo':'bar','foo2':{'zaz':25, 'foo3':{'foo4':'bar4'},'foo5':'bar5'},'num1':1,'list1':['one','two',{'three':3}]}",
|
||||
"[{ 'op': 'test', 'path': '/foo2', 'value': {'foo5':'bar5', 'zaz':25, 'foo3':{'foo4':'bar4'}} }]",
|
||||
0,
|
||||
xstr,
|
||||
&rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
apply_patch(
|
||||
"{'foo':'bar','foo2':{'zaz':25, 'foo3':{'foo4':'bar4'},'foo5':'bar5'},'num1':1,'list1':['one','two',{'three':3}]}",
|
||||
"[{ 'op': 'test', 'path': '/foo2', 'value': {'foo5':'bar5', 'zaz':25, 'foo3':{'foo41':'bar4'}} }]",
|
||||
0,
|
||||
xstr,
|
||||
&rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, JBL_ERROR_PATCH_TEST_FAILED);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
apply_patch(
|
||||
"{'foo':'bar','foo2':{'zaz':25, 'foo3':{'foo4':'bar4'},'foo5':'bar5'},'num1':1,'list1':['one','two',{'three':3}]}",
|
||||
"[{ 'op': 'test', 'path': '/', 'value': {'num1':1, 'foo2':{'foo3':{'foo4':'bar4'}, 'zaz':25, 'foo5':'bar5'},'list1':['one','two',{'three':3}],'foo':'bar'} }]",
|
||||
0,
|
||||
xstr,
|
||||
&rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
apply_patch(
|
||||
"{'foo':'bar','foo2':{'zaz':25, 'foo3':{'foo4':'bar4'},'foo5':'bar5'},'num1':1,'list1':['one','two',{'three':3}]}",
|
||||
"[{ 'op': 'test', 'path': '/list1', 'value':['one','two',{'three':3}] }]",
|
||||
0,
|
||||
xstr,
|
||||
&rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
apply_patch(
|
||||
"{'foo':'bar','foo2':{'zaz':25, 'foo3':{'foo4':'bar4'},'foo5':'bar5'},'num1':1,'list1':['one','two',{'three':3}]}",
|
||||
"[{ 'op': 'test', 'path': '/list1', 'value':['two','one',{'three':3}] }]",
|
||||
0,
|
||||
xstr,
|
||||
&rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, JBL_ERROR_PATCH_TEST_FAILED);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
// A.9. Testing a Value: Error
|
||||
apply_patch("{ 'baz': 'qux'}",
|
||||
"[{ 'op': 'test', 'path': '/baz', 'value': 'bar' }]",
|
||||
0, xstr, &rc);
|
||||
CU_ASSERT_EQUAL(rc, JBL_ERROR_PATCH_TEST_FAILED);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
// A.10. Adding a nested Member Object
|
||||
apply_patch("{'foo': 'bar'}",
|
||||
"[{ 'op': 'add', 'path': '/child', 'value': { 'grandchild': { } } }]",
|
||||
"{'foo':'bar','child':{'grandchild':{}}}", xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
// A.11. Ignoring Unrecognized Elements
|
||||
apply_patch("{'foo': 'bar'}",
|
||||
"[{ 'op': 'add', 'path': '/baz', 'value': 'qux', 'xyz': 123 }]",
|
||||
"{'foo':'bar','baz':'qux'}", xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
// A.12. Adding to a Non-existent Target
|
||||
apply_patch("{'foo': 'bar'}",
|
||||
"[{ 'op': 'add', 'path': '/baz/bat', 'value': 'qux' }]",
|
||||
0, xstr, &rc);
|
||||
CU_ASSERT_EQUAL(rc, JBL_ERROR_PATCH_TARGET_INVALID);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
// A.14. ~ Escape Ordering
|
||||
apply_patch("{'/': 9,'~1': 10}",
|
||||
"[{'op': 'test', 'path': '/~01', 'value': 10}]",
|
||||
"{'/':9,'~1':10}", xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
// A.15. Comparing Strings and Numbers
|
||||
apply_patch("{'/': 9,'~1': 10}",
|
||||
"[{'op': 'test', 'path': '/~01', 'value': '10'}]",
|
||||
"{'/':9,'~1':10}", xstr, &rc);
|
||||
CU_ASSERT_EQUAL(rc, JBL_ERROR_PATCH_TEST_FAILED);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
// A.16. Adding an Array Value
|
||||
apply_patch("{'foo': ['bar']}",
|
||||
"[{'op': 'add', 'path': '/foo/-', 'value': ['abc', 'def'] }]",
|
||||
"{'foo':['bar',['abc','def']]}", xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
// Apply non standard `increment` patch
|
||||
apply_patch("{'foo': 1}",
|
||||
"[{'op': 'increment', 'path': '/foo', 'value': 2}]",
|
||||
"{'foo':3}", xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
// Apply non standard `swap` patch
|
||||
apply_patch("{'foo': ['bar'], 'baz': {'gaz': 11}}",
|
||||
"[{'op': 'swap', 'from': '/foo/0', 'path': '/baz/gaz'}]",
|
||||
"{'foo':[11],'baz':{'gaz':'bar'}}", xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
apply_patch("{'foo': ['bar'], 'baz': {'gaz': 11}}",
|
||||
"[{'op': 'swap', 'from': '/foo/0', 'path': '/baz/zaz'}]",
|
||||
"{'foo':[],'baz':{'gaz':11,'zaz':'bar'}}", xstr, &rc);
|
||||
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
apply_patch("{'foo': 1}",
|
||||
"[{'op': 'increment', 'path': '/foo', 'value': true}]",
|
||||
"{'foo':3}", xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, JBL_ERROR_PATCH_INVALID_VALUE);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
// Apply non standard add_create patch
|
||||
apply_patch("{'foo': {'bar': 1}}",
|
||||
"[{'op': 'add_create', 'path': '/foo/zaz/gaz', 'value': 22}]",
|
||||
"{'foo':{'bar':1,'zaz':{'gaz':22}}}", xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
apply_patch("{'foo': {'bar': 1}}",
|
||||
"[{'op': 'add_create', 'path': '/foo/bar/gaz', 'value': 22}]",
|
||||
"{}", xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, JBL_ERROR_PATCH_TARGET_INVALID);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
apply_patch("{'foo': {'bar': 1}}",
|
||||
"[{'op': 'add_create', 'path': '/zaz/gaz', 'value': [1,2,3]}]",
|
||||
"{'foo':{'bar':1},'zaz':{'gaz':[1,2,3]}}", xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
iwxstr_destroy(xstr);
|
||||
}
|
||||
|
||||
void jbl_test1_7() {
|
||||
iwrc rc;
|
||||
IWXSTR *xstr = iwxstr_new();
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(xstr);
|
||||
|
||||
// #233
|
||||
apply_merge_patch("{'n':'nv'}",
|
||||
"{'a':{'c':'v','d':'k'}}",
|
||||
"{'n':'nv','a':{'c':'v','d':'k'}}", xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
apply_merge_patch("{'a':'b'}",
|
||||
"{'a':'c'}",
|
||||
"{'a':'c'}", xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
|
||||
apply_merge_patch("{'a':'b'}",
|
||||
"{'b':'c'}",
|
||||
"{'a':'b','b':'c'}", xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
apply_merge_patch("{'a':'b'}",
|
||||
"{'a':null}",
|
||||
"{}", xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
apply_merge_patch("{'a':'b','b':'c'}",
|
||||
"{'a':null}",
|
||||
"{'b':'c'}", xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
apply_merge_patch("{'a':['b']}",
|
||||
"{'a':'c'}",
|
||||
"{'a':'c'}", xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
apply_merge_patch("{'a':'c'}",
|
||||
"{'a':['b']}",
|
||||
"{'a':['b']}", xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
apply_merge_patch("{'a':{'b':'c'}}",
|
||||
"{'a':{'b':'d','c':null}}",
|
||||
"{'a':{'b':'d'}}", xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
apply_merge_patch("{'a':[{'b':'c'}]}",
|
||||
"{'a':[1]}",
|
||||
"{'a':[1]}", xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
apply_merge_patch("['a','b']",
|
||||
"['c','d']",
|
||||
"['c','d']", xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
apply_merge_patch("{'a':'b'}",
|
||||
"['c']",
|
||||
"['c']", xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
apply_merge_patch("{'e':null}",
|
||||
"{'a':1}",
|
||||
"{'e':null,'a':1}", xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
apply_merge_patch("[1,2]",
|
||||
"{'a':'b','c':null}",
|
||||
"{'a':'b'}", xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
apply_merge_patch("{}",
|
||||
"{'a':{'bb':{'ccc':null}}}",
|
||||
"{'a':{'bb':{}}}", xstr, &rc);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwxstr_clear(xstr);
|
||||
|
||||
|
||||
iwxstr_destroy(xstr);
|
||||
}
|
||||
|
||||
void jbl_test1_8() {
|
||||
JBL jbl, nested, at;
|
||||
iwrc rc = jbl_create_empty_object(&jbl);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
|
||||
rc = jbl_create_empty_object(&nested);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
|
||||
rc = jbl_set_int64(nested, "nnum", 2233);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
|
||||
rc = jbl_set_int64(jbl, "mynum", 13223);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
|
||||
rc = jbl_set_string(jbl, "foo", "bar");
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
|
||||
rc = jbl_set_nested(jbl, "nested", nested);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
|
||||
rc = jbl_at(jbl, "/mynum", &at);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(at);
|
||||
CU_ASSERT_EQUAL(jbl_get_i64(at), 13223);
|
||||
jbl_destroy(&at);
|
||||
|
||||
rc = jbl_at(jbl, "/foo", &at);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(at);
|
||||
CU_ASSERT_STRING_EQUAL(jbl_get_str(at), "bar");
|
||||
jbl_destroy(&at);
|
||||
|
||||
rc = jbl_at(jbl, "/nested/nnum", &at);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(at);
|
||||
CU_ASSERT_EQUAL(jbl_get_i64(at), 2233);
|
||||
jbl_destroy(&at);
|
||||
|
||||
jbl_destroy(&jbl);
|
||||
jbl_destroy(&nested);
|
||||
}
|
||||
|
||||
void jbl_test1_9(void) {
|
||||
IWPOOL *pool = iwpool_create(512);
|
||||
IWPOOL *cpool = iwpool_create(512);
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(pool);
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(cpool);
|
||||
const char *data = "{\"foo\": \"b\\\"ar\", \"num1\":1223,"
|
||||
"\"n\\\"um2\":10.1226222, "
|
||||
"\"list\":[3,2.1,1,\"one\" \"two\", "
|
||||
"{}, {\"z\":false, \"arr\":[9,8], \"t\":true}]}";
|
||||
|
||||
JBL_NODE n, cn;
|
||||
iwrc rc = jbn_from_json(data, &n, pool);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
|
||||
rc = jbn_clone(n, &cn, cpool);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
|
||||
IWXSTR *xstr = iwxstr_new();
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(xstr);
|
||||
|
||||
iwpool_destroy(pool);
|
||||
|
||||
rc = jbn_as_json(cn, jbl_xstr_json_printer, xstr, 0);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
|
||||
CU_ASSERT_STRING_EQUAL(iwxstr_ptr(
|
||||
xstr),
|
||||
"{\"foo\":\"b\\\"ar\",\"num1\":1223,\"n\\\"um2\":10.1226222,\"list\":[3,2.1,1,\"one\",\"two\",{},{\"z\":false,\"arr\":[9,8],\"t\":true}]}"
|
||||
);
|
||||
|
||||
iwpool_destroy(cpool);
|
||||
iwxstr_destroy(xstr);
|
||||
}
|
||||
|
||||
void jbl_test1_10(void) {
|
||||
IWPOOL *pool = iwpool_create(512);
|
||||
IWPOOL *tpool = iwpool_create(512);
|
||||
IWXSTR *xstr = iwxstr_new();
|
||||
|
||||
const char *src_data = "{\"foo\": \"b\\\"ar\", \"num1\":1223,"
|
||||
"\"n\\\"um2\":10.1226222, "
|
||||
"\"list\":[3,2.1,1,\"one\" \"two\", "
|
||||
"{}, {\"z\":false, \"arr\":[9,8], \"t\":true}]}";
|
||||
const char *tgt_data = "{\"test\":{\"nested1\":22}}";
|
||||
JBL_NODE n1, n2;
|
||||
iwrc rc = jbn_from_json(src_data, &n1, pool);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
|
||||
rc = jbn_from_json(tgt_data, &n2, tpool);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
|
||||
rc = jbn_copy_path(n1, "/list/6/arr", n2, "/test/nested1", false, false, tpool);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
|
||||
rc = jbn_copy_path(n1, "/list/6/t", n2, "/test/t2", false, false, tpool);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
|
||||
rc = jbn_copy_path(n1, "/foo", n2, "/bar", false, false, tpool);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
|
||||
iwpool_destroy(pool);
|
||||
|
||||
rc = jbn_as_json(n2, jbl_xstr_json_printer, xstr, 0);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
|
||||
CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr),
|
||||
"{\"test\":{\"nested1\":[9,8],\"t2\":true},\"bar\":\"b\\\"ar\"}");
|
||||
|
||||
iwpool_destroy(tpool);
|
||||
iwxstr_destroy(xstr);
|
||||
}
|
||||
|
||||
void jbl_test1_11(void) {
|
||||
IWPOOL *pool = iwpool_create(512);
|
||||
IWXSTR *xstr = iwxstr_new();
|
||||
|
||||
const char *src_data = "{\"foo\": \"b\\\"ar\", \"num1\":1223,"
|
||||
"\"n\\\"um2\":10.1226222, "
|
||||
"\"list\":[3,2.1,1,\"one\" \"two\", "
|
||||
"{}, {\"z\":false, \"arr\":[9,8], \"t\":true}]}";
|
||||
const char *tgt_data = "{\"test\":{\"nested1\":22}, \"list\":[0,99]}";
|
||||
|
||||
JBL_NODE n1, n2;
|
||||
iwrc rc = jbn_from_json(src_data, &n1, pool);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
|
||||
rc = jbn_from_json(tgt_data, &n2, pool);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
|
||||
const char *paths[] = { "/foo", "/list/1", 0 };
|
||||
rc = jbn_copy_paths(n1, n2, paths, false, false, pool);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
|
||||
rc = jbn_as_json(n2, jbl_xstr_json_printer, xstr, 0);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
|
||||
CU_ASSERT_STRING_EQUAL(iwxstr_ptr(xstr),
|
||||
"{\"test\":{\"nested1\":22},\"list\":[0,2.1],\"foo\":\"b\\\"ar\"}");
|
||||
|
||||
iwpool_destroy(pool);
|
||||
iwxstr_destroy(xstr);
|
||||
}
|
||||
|
||||
void jbl_test1_12(void) {
|
||||
IWPOOL *pool = iwpool_create_empty();
|
||||
JBL_NODE n;
|
||||
iwrc rc = jbn_from_json("{\"foo\":1.1}", &n, pool);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
iwpool_destroy(pool);
|
||||
}
|
||||
|
||||
int main() {
|
||||
CU_pSuite pSuite = NULL;
|
||||
if (CUE_SUCCESS != CU_initialize_registry()) {
|
||||
return CU_get_error();
|
||||
}
|
||||
pSuite = CU_add_suite("jbl_test1", init_suite, clean_suite);
|
||||
if (NULL == pSuite) {
|
||||
CU_cleanup_registry();
|
||||
return CU_get_error();
|
||||
}
|
||||
if ( (NULL == CU_add_test(pSuite, "jbl_test1_1", jbl_test1_1))
|
||||
|| (NULL == CU_add_test(pSuite, "jbl_test1_2", jbl_test1_2))
|
||||
|| (NULL == CU_add_test(pSuite, "jbl_test1_3", jbl_test1_3))
|
||||
|| (NULL == CU_add_test(pSuite, "jbl_test1_4", jbl_test1_4))
|
||||
|| (NULL == CU_add_test(pSuite, "jbl_test1_5", jbl_test1_5))
|
||||
|| (NULL == CU_add_test(pSuite, "jbl_test1_6", jbl_test1_6))
|
||||
|| (NULL == CU_add_test(pSuite, "jbl_test1_7", jbl_test1_7))
|
||||
|| (NULL == CU_add_test(pSuite, "jbl_test1_8", jbl_test1_8))
|
||||
|| (NULL == CU_add_test(pSuite, "jbl_test1_9", jbl_test1_9))
|
||||
|| (NULL == CU_add_test(pSuite, "jbl_test1_10", jbl_test1_10))
|
||||
|| (NULL == CU_add_test(pSuite, "jbl_test1_11", jbl_test1_11))
|
||||
|| (NULL == CU_add_test(pSuite, "jbl_test1_12", jbl_test1_12))) {
|
||||
CU_cleanup_registry();
|
||||
return CU_get_error();
|
||||
}
|
||||
CU_basic_set_mode(CU_BRM_VERBOSE);
|
||||
CU_basic_run_tests();
|
||||
int ret = CU_get_error() || CU_get_number_of_failures();
|
||||
CU_cleanup_registry();
|
||||
return ret;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+58
-140
@@ -9,180 +9,98 @@ set_target_properties(iwkv_benchmark PROPERTIES COMPILE_FLAGS "-DIW_STATIC")
|
||||
|
||||
enable_language(CXX)
|
||||
|
||||
### LevelDB
|
||||
set(LEVELDB_SOURCE_DIR "${CMAKE_BINARY_DIR}/src/extern_leveldb")
|
||||
set(LEVELDB_BINARY_DIR "${LEVELDB_SOURCE_DIR}")
|
||||
# LevelDB
|
||||
ExternalProject_Add(
|
||||
extern_leveldb
|
||||
GIT_REPOSITORY https://github.com/google/leveldb.git
|
||||
GIT_TAG v1.20
|
||||
PREFIX ${CMAKE_BINARY_DIR}
|
||||
BUILD_IN_SOURCE ON
|
||||
GIT_PROGRESS ON
|
||||
UPDATE_DISCONNECTED ON
|
||||
BUILD_COMMAND make -j${PROCESSOR_COUNT_VAL}
|
||||
CONFIGURE_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
LOG_DOWNLOAD ON
|
||||
LOG_BUILD ON
|
||||
BUILD_BYPRODUCTS "${LEVELDB_BINARY_DIR}/out-static/libleveldb.a"
|
||||
)
|
||||
extern_leveldb
|
||||
GIT_REPOSITORY https://github.com/google/leveldb.git
|
||||
GIT_TAG 1.23
|
||||
PREFIX ${CMAKE_BINARY_DIR}
|
||||
CMAKE_ARGS "-DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}"
|
||||
BUILD_IN_SOURCE OFF
|
||||
GIT_PROGRESS ON
|
||||
UPDATE_DISCONNECTED ON
|
||||
LOG_DOWNLOAD ON
|
||||
LOG_BUILD ON
|
||||
BUILD_BYPRODUCTS "${CMAKE_BINARY_DIR}/lib/libleveldb.a")
|
||||
add_library(libleveldb STATIC IMPORTED)
|
||||
set_target_properties(
|
||||
libleveldb
|
||||
PROPERTIES
|
||||
IMPORTED_LOCATION "${LEVELDB_BINARY_DIR}/out-static/libleveldb.a"
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES CXX
|
||||
)
|
||||
libleveldb
|
||||
PROPERTIES IMPORTED_LOCATION "${CMAKE_BINARY_DIR}/lib/libleveldb.a"
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES CXX)
|
||||
add_dependencies(libleveldb extern_leveldb)
|
||||
include_directories(AFTER "${LEVELDB_SOURCE_DIR}/include")
|
||||
### !LevelDB
|
||||
include_directories(AFTER "${CMAKE_BINARY_DIR}/include")
|
||||
# !LevelDB
|
||||
|
||||
# LevelDB benchmark
|
||||
add_executable(leveldb_benchmark leveldb_benchmark.c)
|
||||
target_link_libraries(leveldb_benchmark libleveldb iowow_s)
|
||||
set_target_properties(leveldb_benchmark
|
||||
PROPERTIES COMPILE_FLAGS "-DIW_STATIC"
|
||||
)
|
||||
set_target_properties(leveldb_benchmark PROPERTIES COMPILE_FLAGS "-DIW_STATIC")
|
||||
|
||||
# LMDB
|
||||
set(LMDB_SOURCE_DIR "${CMAKE_BINARY_DIR}/src/extern_lmdb")
|
||||
set(LMDB_BINARY_DIR "${LMDB_SOURCE_DIR}")
|
||||
ExternalProject_Add(
|
||||
extern_lmdb
|
||||
GIT_REPOSITORY https://github.com/LMDB/lmdb.git
|
||||
GIT_TAG mdb.master
|
||||
PREFIX ${CMAKE_BINARY_DIR}
|
||||
BUILD_IN_SOURCE ON
|
||||
GIT_PROGRESS ON
|
||||
UPDATE_DISCONNECTED ON
|
||||
BUILD_COMMAND make -C libraries/liblmdb
|
||||
CONFIGURE_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
LOG_DOWNLOAD ON
|
||||
LOG_BUILD ON
|
||||
BUILD_BYPRODUCTS "${LMDB_BINARY_DIR}/libraries/liblmdb/liblmdb.a"
|
||||
)
|
||||
extern_lmdb
|
||||
GIT_REPOSITORY https://github.com/LMDB/lmdb.git
|
||||
GIT_TAG mdb.master
|
||||
PREFIX ${CMAKE_BINARY_DIR}
|
||||
BUILD_IN_SOURCE ON
|
||||
GIT_PROGRESS ON
|
||||
UPDATE_DISCONNECTED ON
|
||||
BUILD_COMMAND make -C libraries/liblmdb
|
||||
CONFIGURE_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
LOG_DOWNLOAD ON
|
||||
LOG_BUILD ON
|
||||
BUILD_BYPRODUCTS "${LMDB_BINARY_DIR}/libraries/liblmdb/liblmdb.a")
|
||||
add_library(liblmdb STATIC IMPORTED)
|
||||
set_target_properties(
|
||||
liblmdb
|
||||
PROPERTIES
|
||||
IMPORTED_LOCATION "${LMDB_BINARY_DIR}/libraries/liblmdb/liblmdb.a"
|
||||
)
|
||||
liblmdb PROPERTIES IMPORTED_LOCATION
|
||||
"${LMDB_BINARY_DIR}/libraries/liblmdb/liblmdb.a")
|
||||
add_dependencies(liblmdb extern_lmdb)
|
||||
include_directories(AFTER "${LMDB_SOURCE_DIR}/libraries/liblmdb")
|
||||
|
||||
# LMDB benchmark
|
||||
add_executable(lmdb_benchmark lmdb_benchmark.c)
|
||||
target_link_libraries(lmdb_benchmark liblmdb iowow_s)
|
||||
set_target_properties(lmdb_benchmark
|
||||
PROPERTIES COMPILE_FLAGS "-DIW_STATIC"
|
||||
)
|
||||
|
||||
# KyotoCabinet
|
||||
set(KYC_SOURCE_DIR "${CMAKE_BINARY_DIR}/src/extern_kyc")
|
||||
set(KYC_BINARY_DIR "${KYC_SOURCE_DIR}")
|
||||
ExternalProject_Add(
|
||||
extern_kyc
|
||||
URL https://fallabs.com/kyotocabinet/pkg/kyotocabinet-1.2.77.tar.gz
|
||||
PREFIX ${CMAKE_BINARY_DIR}
|
||||
BUILD_IN_SOURCE ON
|
||||
UPDATE_DISCONNECTED ON
|
||||
BUILD_COMMAND make
|
||||
CONFIGURE_COMMAND ./configure --disable-zlib --prefix=${CMAKE_BINARY_DIR}/src/extern_kyc/install
|
||||
INSTALL_COMMAND make install
|
||||
LOG_DOWNLOAD ON
|
||||
LOG_BUILD OFF
|
||||
LOG_CONFIGURE OFF
|
||||
BUILD_BYPRODUCTS "${KYC_BINARY_DIR}/install/lib/libkyotocabinet.a"
|
||||
)
|
||||
add_library(libkyotocabinet STATIC IMPORTED)
|
||||
set_target_properties(
|
||||
libkyotocabinet
|
||||
PROPERTIES
|
||||
IMPORTED_LOCATION "${KYC_BINARY_DIR}/install/lib/libkyotocabinet.a"
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES CXX
|
||||
)
|
||||
add_dependencies(libkyotocabinet extern_kyc)
|
||||
include_directories(AFTER "${KYC_BINARY_DIR}/install/include")
|
||||
|
||||
add_executable(kyc_benchmark kyc_benchmark.c)
|
||||
target_link_libraries(kyc_benchmark libkyotocabinet iowow_s)
|
||||
set_target_properties(kyc_benchmark
|
||||
PROPERTIES COMPILE_FLAGS "-DIW_STATIC"
|
||||
)
|
||||
|
||||
# TokyoCabinet
|
||||
set(TC_SOURCE_DIR "${CMAKE_BINARY_DIR}/src/extern_tc")
|
||||
set(TC_BINARY_DIR "${TC_SOURCE_DIR}")
|
||||
ExternalProject_Add(
|
||||
extern_tc
|
||||
URL https://fallabs.com/tokyocabinet/tokyocabinet-1.4.48.tar.gz
|
||||
PREFIX ${CMAKE_BINARY_DIR}
|
||||
BUILD_IN_SOURCE ON
|
||||
UPDATE_DISCONNECTED ON
|
||||
BUILD_COMMAND make
|
||||
CONFIGURE_COMMAND ./configure --disable-bzip --disable-exlzo --disable-exlzma --disable-zlib --prefix=${CMAKE_BINARY_DIR}/src/extern_tc/install
|
||||
INSTALL_COMMAND make install
|
||||
LOG_DOWNLOAD ON
|
||||
LOG_BUILD OFF
|
||||
LOG_CONFIGURE OFF
|
||||
BUILD_BYPRODUCTS "${TC_BINARY_DIR}/install/lib/libtokyocabinet.a"
|
||||
)
|
||||
add_library(libtokyocabinet STATIC IMPORTED)
|
||||
set_target_properties(
|
||||
libtokyocabinet
|
||||
PROPERTIES
|
||||
IMPORTED_LOCATION "${TC_BINARY_DIR}/install/lib/libtokyocabinet.a"
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES C
|
||||
)
|
||||
add_dependencies(libtokyocabinet extern_tc)
|
||||
include_directories(AFTER "${TC_BINARY_DIR}/install/include")
|
||||
|
||||
add_executable(tc_benchmark tc_benchmark.c)
|
||||
target_link_libraries(tc_benchmark libtokyocabinet iowow_s)
|
||||
set_target_properties(tc_benchmark
|
||||
PROPERTIES COMPILE_FLAGS "-DIW_STATIC"
|
||||
)
|
||||
set_target_properties(lmdb_benchmark PROPERTIES COMPILE_FLAGS "-DIW_STATIC")
|
||||
|
||||
# Wiredtiger
|
||||
set(WT_SOURCE_DIR "${CMAKE_BINARY_DIR}/src/extern_wt")
|
||||
set(WT_BINARY_DIR "${WT_SOURCE_DIR}")
|
||||
ExternalProject_Add(
|
||||
extern_wt
|
||||
GIT_REPOSITORY https://github.com/wiredtiger/wiredtiger.git
|
||||
PREFIX ${WT_BINARY_DIR}
|
||||
BUILD_IN_SOURCE ON
|
||||
UPDATE_DISCONNECTED ON
|
||||
CONFIGURE_COMMAND sh -c "test -f ${WT_BINARY_DIR}/install/include/wiredtiger.h || (./autogen.sh && ./configure --prefix=${WT_BINARY_DIR}/install)"
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND sh -c "test -f ${WT_BINARY_DIR}/install/include/wiredtiger.h || make install"
|
||||
LOG_DOWNLOAD ON
|
||||
LOG_BUILD OFF
|
||||
LOG_CONFIGURE OFF
|
||||
BUILD_BYPRODUCTS "${WT_BINARY_DIR}/install/lib/libwiredtiger.a"
|
||||
)
|
||||
extern_wt
|
||||
GIT_REPOSITORY https://github.com/wiredtiger/wiredtiger.git
|
||||
PREFIX ${WT_BINARY_DIR}
|
||||
BUILD_IN_SOURCE ON
|
||||
UPDATE_DISCONNECTED ON
|
||||
CONFIGURE_COMMAND
|
||||
sh -c
|
||||
"test -f ${WT_BINARY_DIR}/install/include/wiredtiger.h || (./autogen.sh && ./configure --prefix=${WT_BINARY_DIR}/install)"
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND
|
||||
sh -c
|
||||
"test -f ${WT_BINARY_DIR}/install/include/wiredtiger.h || make install"
|
||||
LOG_DOWNLOAD ON
|
||||
LOG_BUILD OFF
|
||||
LOG_CONFIGURE OFF
|
||||
BUILD_BYPRODUCTS "${WT_BINARY_DIR}/install/lib/libwiredtiger.a")
|
||||
add_library(libwiredtiger STATIC IMPORTED)
|
||||
set_target_properties(
|
||||
libwiredtiger
|
||||
PROPERTIES
|
||||
IMPORTED_LOCATION "${WT_BINARY_DIR}/install/lib/libwiredtiger.a"
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES C
|
||||
)
|
||||
libwiredtiger
|
||||
PROPERTIES IMPORTED_LOCATION "${WT_BINARY_DIR}/install/lib/libwiredtiger.a"
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES C)
|
||||
add_dependencies(libwiredtiger extern_wt)
|
||||
include_directories(AFTER "${WT_BINARY_DIR}/install/include")
|
||||
|
||||
add_executable(wiredtiger_benchmark wiredtiger_benchmark.c)
|
||||
target_link_libraries(wiredtiger_benchmark libwiredtiger iowow_s "-ldl")
|
||||
set_target_properties(wiredtiger_benchmark
|
||||
PROPERTIES COMPILE_FLAGS "-DIW_STATIC"
|
||||
)
|
||||
set_target_properties(wiredtiger_benchmark PROPERTIES COMPILE_FLAGS
|
||||
"-DIW_STATIC")
|
||||
|
||||
# BDB 5.3
|
||||
find_library(BDB_LIBRARY NAMES db)
|
||||
if (BDB_LIBRARY)
|
||||
add_executable(bdb_benchmark bdb_benchmark.c)
|
||||
target_link_libraries(bdb_benchmark db iowow_s)
|
||||
if(BDB_LIBRARY)
|
||||
add_executable(bdb_benchmark bdb_benchmark.c)
|
||||
target_link_libraries(bdb_benchmark db iowow_s)
|
||||
endif()
|
||||
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ finish:
|
||||
return rc;
|
||||
}
|
||||
|
||||
int main() {
|
||||
int main(void) {
|
||||
iwrc rc = run();
|
||||
if (rc) {
|
||||
iwlog_ecode_error3(rc);
|
||||
|
||||
@@ -106,7 +106,7 @@ finish:
|
||||
return rc;
|
||||
}
|
||||
|
||||
int main() {
|
||||
int main(void) {
|
||||
iwrc rc = run();
|
||||
if (rc) {
|
||||
iwlog_ecode_error3(rc);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int main() {
|
||||
int main(void) {
|
||||
IWKV_OPTS opts = {
|
||||
.path = "example1.db",
|
||||
.oflags = IWKV_TRUNC // Cleanup database before open
|
||||
|
||||
+13
-11
@@ -186,7 +186,7 @@ static iwrc _write_wl(IWAL *wal, const void *op, off_t oplen, const uint8_t *dat
|
||||
RCRET(rc);
|
||||
rc = iwp_write(wal->fh, data, (size_t) len);
|
||||
RCRET(rc);
|
||||
} else {
|
||||
} else if (len > 0){
|
||||
assert(bufsz - wal->bufpos >= len);
|
||||
memcpy(wal->buf + wal->bufpos, data, (size_t) len);
|
||||
wal->bufpos += len;
|
||||
@@ -368,9 +368,9 @@ static void _last_fix_and_reset_points(IWAL *wal, uint8_t *wmm, off_t fsz, off_t
|
||||
rp += sizeof(WBRESIZE);
|
||||
break;
|
||||
}
|
||||
case WOP_FIXPOINT: {
|
||||
case WOP_SAVEPOINT: {
|
||||
*fpos = (rp - wmm);
|
||||
rp += sizeof(WBFIXPOINT);
|
||||
rp += sizeof(WBSAVEPOINT);
|
||||
break;
|
||||
}
|
||||
case WOP_RESET: {
|
||||
@@ -549,16 +549,16 @@ static iwrc _rollforward_exl(IWAL *wal, IWFS_EXT *extf, int recover_mode) {
|
||||
RCGO(rc, finish);
|
||||
break;
|
||||
}
|
||||
case WOP_FIXPOINT:
|
||||
case WOP_SAVEPOINT:
|
||||
if (fpos == rp - wmm) { // last fixpoint to
|
||||
WBFIXPOINT wb;
|
||||
WBSAVEPOINT wb;
|
||||
memcpy(&wb, rp, sizeof(wb));
|
||||
iwlog_warn("Database recovered at point of time: %"
|
||||
PRIu64
|
||||
" ms since epoch\n", wb.ts);
|
||||
goto finish;
|
||||
}
|
||||
rp += sizeof(WBFIXPOINT);
|
||||
rp += sizeof(WBSAVEPOINT);
|
||||
break;
|
||||
case WOP_RESET: {
|
||||
rp += sizeof(WBRESET);
|
||||
@@ -649,8 +649,8 @@ static iwrc _checkpoint_exl(IWAL *wal, uint64_t *tsp, bool no_fixpoint) {
|
||||
if (!no_fixpoint) {
|
||||
wal->force_cp = false;
|
||||
wal->force_sp = false;
|
||||
WBFIXPOINT wb = {
|
||||
.id = WOP_FIXPOINT
|
||||
WBSAVEPOINT wb = {
|
||||
.id = WOP_SAVEPOINT
|
||||
};
|
||||
rc = iwp_current_time_ms(&wb.ts, false);
|
||||
RCGO(rc, finish);
|
||||
@@ -748,8 +748,8 @@ iwrc _savepoint_exl(IWAL *wal, uint64_t *tsp, bool sync) {
|
||||
*tsp = 0;
|
||||
}
|
||||
wal->force_sp = false;
|
||||
WBFIXPOINT wbfp = {
|
||||
.id = WOP_FIXPOINT
|
||||
WBSAVEPOINT wbfp = {
|
||||
.id = WOP_SAVEPOINT
|
||||
};
|
||||
iwrc rc = iwp_current_time_ms(&wbfp.ts, false);
|
||||
RCRET(rc);
|
||||
@@ -837,7 +837,9 @@ static void* _cpt_worker_fn(void *op) {
|
||||
}
|
||||
tp.tv_sec += 1; // one sec tick
|
||||
tick_ts = tp.tv_sec * 1000 + (uint64_t) round(tp.tv_nsec / 1.0e6);
|
||||
rci = pthread_cond_timedwait(wal->cpt_condp, wal->mtxp, &tp);
|
||||
do {
|
||||
rci = pthread_cond_timedwait(wal->cpt_condp, wal->mtxp, &tp);
|
||||
} while (rci == EINTR);
|
||||
if (rci && (rci != ETIMEDOUT)) {
|
||||
rc = iwrc_set_errno(IW_ERROR_THREADING_ERRNO, rci);
|
||||
_unlock(wal);
|
||||
|
||||
+3
-3
@@ -41,7 +41,7 @@ typedef enum {
|
||||
WOP_COPY,
|
||||
WOP_WRITE,
|
||||
WOP_RESIZE,
|
||||
WOP_FIXPOINT,
|
||||
WOP_SAVEPOINT,
|
||||
WOP_RESET,
|
||||
WOP_SEP = 127, /**< WAL file separator */
|
||||
} wop_t;
|
||||
@@ -90,11 +90,11 @@ typedef struct WBRESIZE {
|
||||
off_t nsize;
|
||||
} WBRESIZE;
|
||||
|
||||
typedef struct WBFIXPOINT {
|
||||
typedef struct WBSAVEPOINT {
|
||||
uint8_t id;
|
||||
uint8_t pad[3];
|
||||
uint64_t ts;
|
||||
} WBFIXPOINT;
|
||||
} WBSAVEPOINT;
|
||||
#pragma pack(pop)
|
||||
|
||||
iwrc iwal_create(IWKV iwkv, const IWKV_OPTS *opts, IWFS_FSM_OPTS *fsmopts, bool recover_backup);
|
||||
|
||||
+46
-491
@@ -4,17 +4,8 @@
|
||||
#include "iwconv.h"
|
||||
#include <stdalign.h>
|
||||
|
||||
static iwrc _dbcache_fill_lw(IWLCTX *lx);
|
||||
static iwrc _dbcache_get(IWLCTX *lx);
|
||||
static iwrc _dbcache_put_lw(IWLCTX *lx, SBLK *sblk);
|
||||
static void _dbcache_remove_lw(IWLCTX *lx, SBLK *sblk);
|
||||
static void _dbcache_update_lw(IWLCTX *lx, SBLK *sblk);
|
||||
static void _dbcache_destroy_lw(IWDB db);
|
||||
|
||||
#define _wnw_db_wl(db_) _api_db_wlock(db_)
|
||||
|
||||
//-------------------------- GLOBALS
|
||||
|
||||
#ifdef IW_TESTS
|
||||
volatile int8_t iwkv_next_level = -1;
|
||||
#endif
|
||||
@@ -22,8 +13,6 @@ atomic_uint_fast64_t g_trigger;
|
||||
|
||||
#define IWKV_IS_INTERNAL_RC(rc_) ((rc_) > _IWKV_ERROR_END && (rc_) < _IWKV_RC_END)
|
||||
|
||||
//-------------------------- UTILS
|
||||
|
||||
IW_SOFT_INLINE iwrc _to_effective_key(
|
||||
struct _IWDB *db, const IWKV_val *key, IWKV_val *okey,
|
||||
uint8_t nbuf[static IW_VNUMBUFSZ]
|
||||
@@ -442,7 +431,6 @@ static WUR iwrc _db_save(IWDB db, bool newdb, uint8_t *mm) {
|
||||
|
||||
static WUR iwrc _db_load_chain(IWKV iwkv, off_t addr, uint8_t *mm) {
|
||||
iwrc rc;
|
||||
int rci;
|
||||
IWDB db = 0, ndb;
|
||||
if (!addr) {
|
||||
return 0;
|
||||
@@ -450,6 +438,7 @@ static WUR iwrc _db_load_chain(IWKV iwkv, off_t addr, uint8_t *mm) {
|
||||
do {
|
||||
rc = _db_at(iwkv, &ndb, addr, mm);
|
||||
RCRET(rc);
|
||||
|
||||
if (db) {
|
||||
db->next = ndb;
|
||||
ndb->prev = db;
|
||||
@@ -458,21 +447,19 @@ static WUR iwrc _db_load_chain(IWKV iwkv, off_t addr, uint8_t *mm) {
|
||||
}
|
||||
db = ndb;
|
||||
addr = db->next_db_addr;
|
||||
|
||||
rc = iwhmap_put_u32(iwkv->dbs, db->id, db);
|
||||
RCRET(rc);
|
||||
|
||||
iwkv->last_db = db;
|
||||
khiter_t k = kh_put(DBS, iwkv->dbs, db->id, &rci);
|
||||
if (rci != -1) {
|
||||
kh_value(iwkv->dbs, k) = db;
|
||||
} else {
|
||||
return iwrc_set_errno(IW_ERROR_ALLOC, errno);
|
||||
}
|
||||
} while (db->next_db_addr);
|
||||
return rc;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void _db_release_lw(IWDB *dbp) {
|
||||
assert(dbp && *dbp);
|
||||
IWDB db = *dbp;
|
||||
_dbcache_destroy_lw(db);
|
||||
pthread_rwlock_destroy(&db->rwl);
|
||||
pthread_spin_destroy(&db->cursors_slk);
|
||||
free(db);
|
||||
@@ -552,12 +539,11 @@ static WUR iwrc _db_destroy_lw(IWDB *dbp) {
|
||||
IWFS_FSM *fsm = &iwkv->fsm;
|
||||
uint32_t first_sblkn;
|
||||
|
||||
khiter_t k = kh_get(DBS, iwkv->dbs, db->id);
|
||||
if (k == kh_end(iwkv->dbs)) {
|
||||
if (!iwhmap_get_u32(iwkv->dbs, db->id)) {
|
||||
iwlog_ecode_error3(IW_ERROR_INVALID_STATE);
|
||||
return IW_ERROR_INVALID_STATE;
|
||||
}
|
||||
kh_del(DBS, iwkv->dbs, k);
|
||||
iwhmap_remove_u32(iwkv->dbs, db->id);
|
||||
|
||||
rc = fsm->acquire_mmap(fsm, 0, &mm, 0);
|
||||
RCRET(rc);
|
||||
@@ -657,14 +643,10 @@ static WUR iwrc _db_create_lw(IWKV iwkv, dbid_t dbid, iwdb_flags_t dbflg, IWDB *
|
||||
} else if (iwkv->last_db) {
|
||||
iwkv->last_db->next = db;
|
||||
}
|
||||
|
||||
RCC(rc, finish, iwhmap_put_u32(iwkv->dbs, db->id, db));
|
||||
iwkv->last_db = db;
|
||||
khiter_t k = kh_put(DBS, iwkv->dbs, db->id, &rci);
|
||||
if (rci != -1) {
|
||||
kh_value(iwkv->dbs, k) = db;
|
||||
} else {
|
||||
rc = iwrc_set_errno(IW_ERROR_ALLOC, errno);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
rc = fsm->acquire_mmap(fsm, 0, &mm, 0);
|
||||
RCGO(rc, finish);
|
||||
rc = _db_save(db, true, mm);
|
||||
@@ -1226,8 +1208,10 @@ start:
|
||||
}
|
||||
memcpy(wp, key->data, key->size);
|
||||
wp += key->size;
|
||||
memcpy(wp, uval->data, uval->size);
|
||||
wp += uval->size;
|
||||
if (uval->size) {
|
||||
memcpy(wp, uval->data, uval->size);
|
||||
wp += uval->size;
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
assert(wp - sptr == kvp->len);
|
||||
#endif
|
||||
@@ -1339,7 +1323,6 @@ finish:
|
||||
IW_INLINE void _sblk_release(IWLCTX *lx, SBLK **sblkp) {
|
||||
assert(sblkp && *sblkp);
|
||||
SBLK *sblk = *sblkp;
|
||||
sblk->flags &= ~SBLK_CACHE_FLAGS; // clear cache flags
|
||||
sblk->flags &= ~SBLK_DURTY; // clear dirty flag
|
||||
sblk->kvblk = 0;
|
||||
*sblkp = 0;
|
||||
@@ -1397,7 +1380,6 @@ IW_INLINE WUR iwrc _sblk_destroy(IWLCTX *lx, SBLK **sblkp) {
|
||||
lx->db->lcnt[sblk->lvl]--;
|
||||
lx->db->flags |= SBLK_DURTY;
|
||||
}
|
||||
_dbcache_remove_lw(lx, sblk);
|
||||
if (lx->db->iwkv->fmt_version > 1) {
|
||||
off_t paddr;
|
||||
if (_sblk_is_only_one_on_page_v2(lx, mm, sblk, &paddr)) {
|
||||
@@ -1467,7 +1449,7 @@ static WUR iwrc _sblk_create_v1(IWLCTX *lx, uint8_t nlevel, uint8_t kvbpow, off_
|
||||
sblk->db->lcnt[nlevel]++;
|
||||
sblk->db->flags |= SBLK_DURTY;
|
||||
sblk->addr = baddr;
|
||||
sblk->flags = (SBLK_DURTY | SBLK_CACHE_PUT);
|
||||
sblk->flags = SBLK_DURTY;
|
||||
sblk->lvl = nlevel;
|
||||
sblk->p0 = 0;
|
||||
memset(sblk->n, 0, sizeof(sblk->n));
|
||||
@@ -1767,9 +1749,6 @@ static WUR iwrc _sblk_sync_mm(IWLCTX *lx, SBLK *sblk, uint8_t *mm) {
|
||||
if (sblk->kvblk && (sblk->kvblk->flags & KVBLK_DURTY)) {
|
||||
IWRC(_kvblk_sync_mm(sblk->kvblk, mm), rc);
|
||||
}
|
||||
if (sblk->flags & SBLK_CACHE_UPDATE) {
|
||||
_dbcache_update_lw(lx, sblk);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -1910,9 +1889,6 @@ static WUR iwrc _sblk_addkv2(
|
||||
sblk->pi[idx] = kvidx;
|
||||
if (sblk->kvblkn != ADDR2BLK(kvblk->addr)) {
|
||||
sblk->kvblkn = ADDR2BLK(kvblk->addr);
|
||||
if (!(sblk->flags & SBLK_CACHE_FLAGS)) {
|
||||
sblk->flags |= SBLK_CACHE_UPDATE;
|
||||
}
|
||||
}
|
||||
++sblk->pnum;
|
||||
sblk->flags |= SBLK_DURTY;
|
||||
@@ -1935,9 +1911,6 @@ static WUR iwrc _sblk_addkv2(
|
||||
} else {
|
||||
sblk->flags &= ~SBLK_FULL_LKEY;
|
||||
}
|
||||
if (!(sblk->flags & SBLK_CACHE_FLAGS)) {
|
||||
sblk->flags |= SBLK_CACHE_UPDATE;
|
||||
}
|
||||
}
|
||||
if (!raw_key) {
|
||||
// Update active cursors inside this block
|
||||
@@ -2000,15 +1973,9 @@ static WUR iwrc _sblk_addkv(SBLK *sblk, IWLCTX *lx) {
|
||||
} else {
|
||||
sblk->flags &= ~SBLK_FULL_LKEY;
|
||||
}
|
||||
if (!(sblk->flags & SBLK_CACHE_FLAGS)) {
|
||||
sblk->flags |= SBLK_CACHE_UPDATE;
|
||||
}
|
||||
}
|
||||
if (sblk->kvblkn != ADDR2BLK(kvblk->addr)) {
|
||||
sblk->kvblkn = ADDR2BLK(kvblk->addr);
|
||||
if (!(sblk->flags & SBLK_CACHE_FLAGS)) {
|
||||
sblk->flags |= SBLK_CACHE_UPDATE;
|
||||
}
|
||||
}
|
||||
sblk->flags |= SBLK_DURTY;
|
||||
|
||||
@@ -2048,9 +2015,6 @@ static WUR iwrc _sblk_updatekv(
|
||||
RCRET(rc);
|
||||
if (sblk->kvblkn != ADDR2BLK(kvblk->addr)) {
|
||||
sblk->kvblkn = ADDR2BLK(kvblk->addr);
|
||||
if (!(sblk->flags & SBLK_CACHE_FLAGS)) {
|
||||
sblk->flags |= SBLK_CACHE_UPDATE;
|
||||
}
|
||||
}
|
||||
sblk->pi[idx] = kvidx;
|
||||
sblk->flags |= SBLK_DURTY;
|
||||
@@ -2079,9 +2043,6 @@ static WUR iwrc _sblk_rmkv(SBLK *sblk, uint8_t idx) {
|
||||
|
||||
if (sblk->kvblkn != ADDR2BLK(kvblk->addr)) {
|
||||
sblk->kvblkn = ADDR2BLK(kvblk->addr);
|
||||
if (!(sblk->flags & SBLK_CACHE_FLAGS)) {
|
||||
sblk->flags |= SBLK_CACHE_UPDATE;
|
||||
}
|
||||
}
|
||||
--sblk->pnum;
|
||||
sblk->flags |= SBLK_DURTY;
|
||||
@@ -2110,12 +2071,8 @@ static WUR iwrc _sblk_rmkv(SBLK *sblk, uint8_t idx) {
|
||||
} else {
|
||||
sblk->flags &= ~SBLK_FULL_LKEY;
|
||||
}
|
||||
if (!(sblk->flags & SBLK_CACHE_FLAGS)) {
|
||||
sblk->flags |= SBLK_CACHE_UPDATE;
|
||||
}
|
||||
} else {
|
||||
sblk->lkl = 0;
|
||||
sblk->flags |= SBLK_CACHE_REMOVE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2245,8 +2202,7 @@ static WUR iwrc _lx_find_bounds(IWLCTX *lx) {
|
||||
memcpy(dblk, s, sizeof(*dblk));
|
||||
}
|
||||
if (!lx->lower) {
|
||||
rc = _dbcache_get(lx);
|
||||
RCRET(rc);
|
||||
lx->lower = &lx->dblk;
|
||||
}
|
||||
if (lx->nlvl > dblk->lvl) {
|
||||
// New level in DB
|
||||
@@ -2318,15 +2274,9 @@ static iwrc _lx_release_mm(IWLCTX *lx, uint8_t *mm) {
|
||||
RCGO(rc, finish);
|
||||
}
|
||||
if (lx->nb) {
|
||||
if (lx->nb->flags & SBLK_CACHE_PUT) {
|
||||
rc = _dbcache_put_lw(lx, lx->nb);
|
||||
}
|
||||
_sblk_release(lx, &lx->nb);
|
||||
RCGO(rc, finish);
|
||||
}
|
||||
if (lx->cache_reload) {
|
||||
rc = _dbcache_fill_lw(lx);
|
||||
}
|
||||
|
||||
finish:
|
||||
lx->destroy_addr = 0;
|
||||
@@ -2769,341 +2719,6 @@ finish:
|
||||
return rc;
|
||||
}
|
||||
|
||||
//-------------------------- CACHE
|
||||
|
||||
static void _dbcache_destroy_lw(IWDB db) {
|
||||
free(db->cache.nodes);
|
||||
memset(&db->cache, 0, sizeof(db->cache));
|
||||
}
|
||||
|
||||
IW_INLINE uint8_t _dbcache_lvl(uint8_t lvl) {
|
||||
uint8_t clvl = (lvl >= DBCACHE_LEVELS) ? (lvl - DBCACHE_LEVELS + 1) : DBCACHE_MIN_LEVEL;
|
||||
if (clvl < DBCACHE_MIN_LEVEL) {
|
||||
clvl = DBCACHE_MIN_LEVEL;
|
||||
}
|
||||
return clvl;
|
||||
}
|
||||
|
||||
static WUR iwrc _dbcache_cmp_nodes(const void *v1, const void *v2, void *op, int *res) {
|
||||
iwrc rc = 0;
|
||||
uint8_t *mm = 0;
|
||||
IWLCTX *lx = op;
|
||||
IWDB db = lx->db;
|
||||
IWFS_FSM *fsm = &db->iwkv->fsm;
|
||||
iwdb_flags_t dbflg = db->dbflg;
|
||||
int rv = 0, step;
|
||||
|
||||
const DBCNODE *cn1 = v1, *cn2 = v2;
|
||||
uint8_t *k1 = (uint8_t*) cn1->lk, *k2 = (uint8_t*) cn2->lk;
|
||||
uint32_t kl1 = cn1->lkl, kl2 = cn2->lkl;
|
||||
KVBLK *kb;
|
||||
|
||||
if (!kl1 && cn1->fullkey) {
|
||||
kl1 = cn1->sblkn;
|
||||
}
|
||||
if (!kl2 && cn2->fullkey) {
|
||||
kl2 = cn2->sblkn;
|
||||
}
|
||||
|
||||
IWKV_val key2 = {
|
||||
.size = kl2,
|
||||
.data = k2
|
||||
};
|
||||
|
||||
if (dbflg & IWDB_COMPOUND_KEYS) {
|
||||
IW_READVNUMBUF64(k2, key2.compound, step);
|
||||
key2.size -= step;
|
||||
key2.data = (char*) key2.data + step;
|
||||
}
|
||||
|
||||
rv = _cmp_keys_prefix(dbflg, k1, kl1, &key2);
|
||||
|
||||
if ((rv == 0) && !(dbflg & (IWDB_VNUM64_KEYS | IWDB_REALNUM_KEYS))) {
|
||||
if (!cn1->fullkey || !cn2->fullkey) {
|
||||
rc = fsm->acquire_mmap(fsm, 0, &mm, 0);
|
||||
RCRET(rc);
|
||||
if (!cn1->fullkey) {
|
||||
rc = _kvblk_at_mm(lx, BLK2ADDR(cn1->kblkn), mm, 0, &kb);
|
||||
RCGO(rc, finish);
|
||||
rc = _kvblk_key_peek(kb, cn1->k0idx, mm, &k1, &kl1);
|
||||
RCGO(rc, finish);
|
||||
}
|
||||
if (!cn2->fullkey) {
|
||||
rc = _kvblk_at_mm(lx, BLK2ADDR(cn2->kblkn), mm, 0, &kb);
|
||||
RCGO(rc, finish);
|
||||
rc = _kvblk_key_peek(kb, cn2->k0idx, mm, &k2, &kl2);
|
||||
RCGO(rc, finish);
|
||||
key2.size = kl2;
|
||||
key2.data = k2;
|
||||
if (dbflg & IWDB_COMPOUND_KEYS) {
|
||||
IW_READVNUMBUF64(k2, key2.compound, step);
|
||||
key2.size -= step;
|
||||
key2.data = (char*) key2.data + step;
|
||||
}
|
||||
}
|
||||
|
||||
rv = _cmp_keys(dbflg, k1, kl1, &key2);
|
||||
} else if (dbflg & IWDB_COMPOUND_KEYS) {
|
||||
int64_t c1, c2 = key2.compound;
|
||||
IW_READVNUMBUF64(k1, c1, step);
|
||||
kl1 -= step;
|
||||
if (key2.size == kl1) {
|
||||
rv = c1 > c2 ? -1 : c1 < c2 ? 1 : 0;
|
||||
} else {
|
||||
rv = (int) key2.size - (int) kl1;
|
||||
}
|
||||
} else {
|
||||
rv = (int) kl2 - (int) kl1;
|
||||
}
|
||||
}
|
||||
|
||||
finish:
|
||||
*res = rv;
|
||||
if (mm) {
|
||||
fsm->release_mmap(fsm);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static WUR iwrc _dbcache_fill_lw(IWLCTX *lx) {
|
||||
iwrc rc = 0;
|
||||
IWDB db = lx->db;
|
||||
lx->cache_reload = 0;
|
||||
if (!lx->dblk.addr) {
|
||||
SBLK *s;
|
||||
rc = _sblk_at(lx, lx->db->addr, 0, &s);
|
||||
RCRET(rc);
|
||||
memcpy(&lx->dblk, s, sizeof(lx->dblk));
|
||||
}
|
||||
SBLK *sdb = &lx->dblk;
|
||||
SBLK *sblk = sdb;
|
||||
DBCACHE *c = &db->cache;
|
||||
assert(lx->db->addr == sdb->addr);
|
||||
c->num = 0;
|
||||
if (c->nodes) {
|
||||
free(c->nodes);
|
||||
c->nodes = 0;
|
||||
}
|
||||
if (sdb->lvl < DBCACHE_MIN_LEVEL) {
|
||||
c->open = true;
|
||||
return 0;
|
||||
}
|
||||
c->lvl = _dbcache_lvl(sdb->lvl);
|
||||
c->nsize = (lx->db->dbflg & IWDB_VNUM64_KEYS) ? DBCNODE_VNUM_SZ : DBCNODE_STR_SZ;
|
||||
c->asize = c->nsize * ((1U << DBCACHE_LEVELS) + DBCACHE_ALLOC_STEP);
|
||||
|
||||
size_t nsize = c->nsize;
|
||||
c->nodes = malloc(c->asize);
|
||||
if (!c->nodes) {
|
||||
c->open = false;
|
||||
return iwrc_set_errno(IW_ERROR_ALLOC, errno);
|
||||
}
|
||||
blkn_t n;
|
||||
uint8_t *wp;
|
||||
size_t num = 0;
|
||||
while ((n = sblk->n[c->lvl])) {
|
||||
rc = _sblk_at(lx, BLK2ADDR(n), 0, &sblk);
|
||||
RCRET(rc);
|
||||
if (offsetof(DBCNODE, lk) + sblk->lkl > nsize) {
|
||||
free(c->nodes);
|
||||
c->nodes = 0;
|
||||
rc = IWKV_ERROR_CORRUPTED;
|
||||
iwlog_ecode_error3(rc);
|
||||
return rc;
|
||||
}
|
||||
DBCNODE cn = {
|
||||
.lkl = sblk->lkl,
|
||||
.fullkey = (sblk->flags & SBLK_FULL_LKEY),
|
||||
.k0idx = sblk->pi[0],
|
||||
.sblkn = ADDR2BLK(sblk->addr),
|
||||
.kblkn = sblk->kvblkn
|
||||
};
|
||||
if (c->asize < nsize * (num + 1)) {
|
||||
c->asize += (nsize * DBCACHE_ALLOC_STEP);
|
||||
wp = (uint8_t*) c->nodes;
|
||||
DBCNODE *nn = realloc(c->nodes, c->asize);
|
||||
if (!nn) {
|
||||
rc = iwrc_set_errno(IW_ERROR_ALLOC, errno);
|
||||
free(wp);
|
||||
return rc;
|
||||
}
|
||||
c->nodes = nn;
|
||||
}
|
||||
wp = (uint8_t*) c->nodes + nsize * num;
|
||||
memcpy(wp, &cn, offsetof(DBCNODE, lk));
|
||||
wp += offsetof(DBCNODE, lk);
|
||||
memcpy(wp, sblk->lk, sblk->lkl);
|
||||
++num;
|
||||
}
|
||||
c->num = num;
|
||||
c->open = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static WUR iwrc _dbcache_get(IWLCTX *lx) {
|
||||
iwrc rc = 0;
|
||||
off_t idx;
|
||||
bool found;
|
||||
DBCNODE *n;
|
||||
alignas(DBCNODE) uint8_t dbcbuf[255];
|
||||
IWDB db = lx->db;
|
||||
DBCACHE *cache = &db->cache;
|
||||
const IWKV_val *key = lx->key;
|
||||
if ((lx->nlvl > -1) || (cache->num < 1)) {
|
||||
lx->lower = &lx->dblk;
|
||||
return 0;
|
||||
}
|
||||
assert(cache->nodes);
|
||||
size_t lxksiz = key->size;
|
||||
if (db->dbflg & IWDB_COMPOUND_KEYS) {
|
||||
lxksiz += IW_VNUMSIZE(key->compound);
|
||||
}
|
||||
|
||||
if (sizeof(DBCNODE) + lxksiz <= sizeof(dbcbuf)) {
|
||||
n = (DBCNODE*) dbcbuf;
|
||||
} else {
|
||||
n = malloc(sizeof(DBCNODE) + lxksiz);
|
||||
if (!n) {
|
||||
return iwrc_set_errno(IW_ERROR_ALLOC, errno);
|
||||
}
|
||||
}
|
||||
n->sblkn = (uint32_t) lxksiz; // `sblkn` used to store key size (to keep DBCNODE compact)
|
||||
n->kblkn = 0;
|
||||
n->fullkey = 1;
|
||||
n->lkl = 0;
|
||||
n->k0idx = 0;
|
||||
|
||||
uint8_t *wp = (uint8_t*) n + offsetof(DBCNODE, lk);
|
||||
if (db->dbflg & IWDB_COMPOUND_KEYS) {
|
||||
size_t step;
|
||||
char vbuf[IW_VNUMBUFSZ];
|
||||
IW_SETVNUMBUF(step, vbuf, key->compound);
|
||||
memcpy(wp, vbuf, step);
|
||||
wp += step;
|
||||
}
|
||||
memcpy(wp, key->data, key->size);
|
||||
|
||||
idx = iwarr_sorted_find2(cache->nodes, cache->num, cache->nsize, n, lx, &found, _dbcache_cmp_nodes);
|
||||
if (idx > 0) {
|
||||
DBCNODE *fn = (DBCNODE*) ((uint8_t*) cache->nodes + (idx - 1) * cache->nsize);
|
||||
assert(fn && idx - 1 < cache->num);
|
||||
rc = _sblk_at(lx, BLK2ADDR(fn->sblkn), 0, &lx->lower);
|
||||
} else {
|
||||
lx->lower = &lx->dblk;
|
||||
}
|
||||
if ((uint8_t*) n != dbcbuf) {
|
||||
free(n);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static WUR iwrc _dbcache_put_lw(IWLCTX *lx, SBLK *sblk) {
|
||||
off_t idx;
|
||||
bool found;
|
||||
IWDB db = lx->db;
|
||||
alignas(DBCNODE) uint8_t dbcbuf[255];
|
||||
DBCNODE *n = (DBCNODE*) dbcbuf;
|
||||
DBCACHE *cache = &db->cache;
|
||||
size_t nsize = cache->nsize;
|
||||
|
||||
sblk->flags &= ~SBLK_CACHE_PUT;
|
||||
assert(sizeof(*cache) + sblk->lkl <= sizeof(dbcbuf));
|
||||
if ((sblk->pnum < 1) || (sblk->lvl < cache->lvl)) {
|
||||
return 0;
|
||||
}
|
||||
if ((sblk->lvl >= cache->lvl + DBCACHE_LEVELS) || !cache->nodes) { // need to reload full cache
|
||||
lx->cache_reload = 1;
|
||||
return 0;
|
||||
}
|
||||
if (!sblk->kvblk) {
|
||||
assert(sblk->kvblk);
|
||||
return IW_ERROR_INVALID_STATE;
|
||||
}
|
||||
n->lkl = sblk->lkl;
|
||||
n->fullkey = (sblk->flags & SBLK_FULL_LKEY);
|
||||
n->k0idx = sblk->pi[0];
|
||||
n->sblkn = ADDR2BLK(sblk->addr);
|
||||
n->kblkn = sblk->kvblkn;
|
||||
memcpy((uint8_t*) n + offsetof(DBCNODE, lk), sblk->lk, sblk->lkl);
|
||||
|
||||
idx = iwarr_sorted_find2(cache->nodes, cache->num, nsize, n, lx, &found, _dbcache_cmp_nodes);
|
||||
assert(!found);
|
||||
|
||||
if (cache->asize <= cache->num * nsize) {
|
||||
size_t nsz = cache->asize + (nsize * DBCACHE_ALLOC_STEP);
|
||||
DBCNODE *nodes = realloc(cache->nodes, nsz);
|
||||
if (!nodes) {
|
||||
iwrc rc = iwrc_set_errno(IW_ERROR_ALLOC, errno);
|
||||
free(cache->nodes);
|
||||
cache->nodes = 0;
|
||||
return rc;
|
||||
}
|
||||
cache->asize = nsz;
|
||||
cache->nodes = nodes;
|
||||
}
|
||||
|
||||
uint8_t *cptr = (uint8_t*) cache->nodes;
|
||||
if (cache->num != idx) {
|
||||
memmove(cptr + (idx + 1) * nsize, cptr + idx * nsize, (cache->num - idx) * nsize);
|
||||
}
|
||||
memcpy(cptr + idx * nsize, n, nsize);
|
||||
++cache->num;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void _dbcache_remove_lw(IWLCTX *lx, SBLK *sblk) {
|
||||
IWDB db = lx->db;
|
||||
DBCACHE *cache = &db->cache;
|
||||
sblk->flags &= ~SBLK_CACHE_REMOVE;
|
||||
if ((sblk->lvl < cache->lvl) || (cache->num < 1)) {
|
||||
return;
|
||||
}
|
||||
if ((cache->lvl > DBCACHE_MIN_LEVEL) && (lx->dblk.lvl < sblk->lvl)) {
|
||||
// Database level reduced so we need to shift cache down
|
||||
lx->cache_reload = 1;
|
||||
return;
|
||||
}
|
||||
blkn_t sblkn = ADDR2BLK(sblk->addr);
|
||||
size_t num = cache->num;
|
||||
size_t nsize = cache->nsize;
|
||||
uint8_t *rp = (uint8_t*) cache->nodes;
|
||||
for (size_t i = 0; i < num; ++i) {
|
||||
DBCNODE *n = (DBCNODE*) (rp + i * nsize);
|
||||
if (sblkn == n->sblkn) {
|
||||
if (i < num - 1) {
|
||||
memmove(rp + i * nsize, rp + (i + 1) * nsize, (num - i - 1) * nsize);
|
||||
}
|
||||
--cache->num;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void _dbcache_update_lw(IWLCTX *lx, SBLK *sblk) {
|
||||
IWDB db = lx->db;
|
||||
DBCACHE *cache = &db->cache;
|
||||
assert(sblk->pnum > 0);
|
||||
sblk->flags &= ~SBLK_CACHE_UPDATE;
|
||||
if ((sblk->lvl < cache->lvl) || (cache->num < 1)) {
|
||||
return;
|
||||
}
|
||||
blkn_t sblkn = ADDR2BLK(sblk->addr);
|
||||
size_t num = cache->num;
|
||||
size_t nsize = cache->nsize;
|
||||
uint8_t *rp = (uint8_t*) cache->nodes;
|
||||
for (size_t i = 0; i < num; ++i) {
|
||||
DBCNODE *n = (DBCNODE*) (rp + i * nsize);
|
||||
if (sblkn == n->sblkn) {
|
||||
n->kblkn = sblk->kvblkn;
|
||||
n->lkl = sblk->lkl;
|
||||
n->fullkey = (sblk->flags & SBLK_FULL_LKEY);
|
||||
n->k0idx = sblk->pi[0];
|
||||
memcpy((uint8_t*) n + offsetof(DBCNODE, lk), sblk->lk, sblk->lkl);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------- CURSOR
|
||||
|
||||
IW_INLINE WUR iwrc _cursor_get_ge_idx(IWLCTX *lx, IWKV_cursor_op op, uint8_t *oidx) {
|
||||
@@ -3552,7 +3167,8 @@ iwrc iwkv_open(const IWKV_OPTS *opts, IWKV *iwkvp) {
|
||||
.bpow = IWKV_FSM_BPOW, // 64 bytes block size
|
||||
.hdrlen = KVHDRSZ, // Size of custom file header
|
||||
.oflags = ((oflags & IWKV_RDONLY) ? IWFSM_NOLOCKS : 0),
|
||||
.mmap_all = true
|
||||
.mmap_all = true,
|
||||
.mmap_opts = IWFS_MMAP_RANDOM
|
||||
};
|
||||
#ifndef NDEBUG
|
||||
fsmopts.oflags |= IWFSM_STRICT;
|
||||
@@ -3564,17 +3180,14 @@ iwrc iwkv_open(const IWKV_OPTS *opts, IWKV *iwkvp) {
|
||||
fsmopts.exfile.file.lock_mode |= IWP_NBLOCK;
|
||||
}
|
||||
// Init WAL
|
||||
rc = iwal_create(iwkv, opts, &fsmopts, has_online_bkp);
|
||||
RCGO(rc, finish);
|
||||
RCC(rc, finish, iwal_create(iwkv, opts, &fsmopts, has_online_bkp));
|
||||
|
||||
// Now open database file
|
||||
rc = iwfs_fsmfile_open(&iwkv->fsm, &fsmopts);
|
||||
RCGO(rc, finish);
|
||||
RCC(rc, finish, iwfs_fsmfile_open(&iwkv->fsm, &fsmopts));
|
||||
RCB(finish, iwkv->dbs = iwhmap_create_u32(0));
|
||||
|
||||
IWFS_FSM *fsm = &iwkv->fsm;
|
||||
iwkv->dbs = kh_init(DBS);
|
||||
rc = fsm->state(fsm, &fsmstate);
|
||||
RCGO(rc, finish);
|
||||
RCC(rc, finish, fsm->state(fsm, &fsmstate));
|
||||
|
||||
// Database header: [magic:u4, first_addr:u8, db_format_version:u4]
|
||||
if (fsmstate.exfile.file.ostatus & IWFS_OPEN_NEW) {
|
||||
@@ -3583,15 +3196,12 @@ iwrc iwkv_open(const IWKV_OPTS *opts, IWKV *iwkvp) {
|
||||
IW_WRITELV(wp, lv, IWKV_MAGIC);
|
||||
wp += sizeof(llv); // skip first db addr
|
||||
IW_WRITELV(wp, lv, iwkv->fmt_version);
|
||||
rc = fsm->writehdr(fsm, 0, hdr, sizeof(hdr));
|
||||
RCGO(rc, finish);
|
||||
rc = fsm->sync(fsm, 0);
|
||||
RCGO(rc, finish);
|
||||
RCC(rc, finish, fsm->writehdr(fsm, 0, hdr, sizeof(hdr)));
|
||||
RCC(rc, finish, fsm->sync(fsm, 0));
|
||||
} else {
|
||||
off_t dbaddr; // first database address
|
||||
uint8_t hdr[KVHDRSZ];
|
||||
rc = fsm->readhdr(fsm, 0, hdr, KVHDRSZ);
|
||||
RCGO(rc, finish);
|
||||
RCC(rc, finish, fsm->readhdr(fsm, 0, hdr, KVHDRSZ));
|
||||
rp = hdr; // -V507
|
||||
IW_READLV(rp, lv, lv);
|
||||
IW_READLLV(rp, llv, dbaddr);
|
||||
@@ -3611,9 +3221,8 @@ iwrc iwkv_open(const IWKV_OPTS *opts, IWKV *iwkvp) {
|
||||
} else {
|
||||
iwkv->pklen = PREFIX_KEY_LEN_V2;
|
||||
}
|
||||
rc = fsm->acquire_mmap(fsm, 0, &mm, 0);
|
||||
RCGO(rc, finish);
|
||||
rc = _db_load_chain(iwkv, dbaddr, mm);
|
||||
RCC(rc, finish, fsm->acquire_mmap(fsm, 0, &mm, 0));
|
||||
RCC(rc, finish, _db_load_chain(iwkv, dbaddr, mm));
|
||||
fsm->release_mmap(fsm);
|
||||
}
|
||||
(*iwkvp)->open = true;
|
||||
@@ -3653,9 +3262,10 @@ iwrc iwkv_close(IWKV *iwkvp) {
|
||||
IWRC(iwkv->fsm.close(&iwkv->fsm), rc);
|
||||
// Below the memory cleanup only
|
||||
if (iwkv->dbs) {
|
||||
kh_destroy(DBS, iwkv->dbs);
|
||||
iwhmap_destroy(iwkv->dbs);
|
||||
iwkv->dbs = 0;
|
||||
}
|
||||
|
||||
iwkv_exclusive_unlock(iwkv);
|
||||
pthread_rwlock_destroy(&iwkv->rwl);
|
||||
pthread_mutex_destroy(&iwkv->wk_mtx);
|
||||
@@ -3709,13 +3319,12 @@ iwrc iwkv_db(IWKV iwkv, uint32_t dbid, iwdb_flags_t dbflg, IWDB *dbp) {
|
||||
iwrc rc = 0;
|
||||
IWDB db = 0;
|
||||
*dbp = 0;
|
||||
|
||||
API_RLOCK(iwkv, rci);
|
||||
khiter_t ki = kh_get(DBS, iwkv->dbs, dbid);
|
||||
if (ki != kh_end(iwkv->dbs)) {
|
||||
db = kh_value(iwkv->dbs, ki);
|
||||
}
|
||||
db = iwhmap_get_u32(iwkv->dbs, dbid);
|
||||
API_UNLOCK(iwkv, rci, rc);
|
||||
RCRET(rc);
|
||||
|
||||
if (db) {
|
||||
if (db->dbflg != dbflg) {
|
||||
return IWKV_ERROR_INCOMPATIBLE_DB_MODE;
|
||||
@@ -3728,10 +3337,8 @@ iwrc iwkv_db(IWKV iwkv, uint32_t dbid, iwdb_flags_t dbflg, IWDB *dbp) {
|
||||
}
|
||||
rc = iwkv_exclusive_lock(iwkv);
|
||||
RCRET(rc);
|
||||
ki = kh_get(DBS, iwkv->dbs, dbid);
|
||||
if (ki != kh_end(iwkv->dbs)) {
|
||||
db = kh_value(iwkv->dbs, ki);
|
||||
}
|
||||
|
||||
db = iwhmap_get_u32(iwkv->dbs, dbid);
|
||||
if (db) {
|
||||
if (db->dbflg != dbflg) {
|
||||
return IWKV_ERROR_INCOMPATIBLE_DB_MODE;
|
||||
@@ -3756,15 +3363,17 @@ iwrc iwkv_new_db(IWKV iwkv, iwdb_flags_t dbflg, uint32_t *dbidp, IWDB *dbp) {
|
||||
uint32_t dbid = 0;
|
||||
iwrc rc = iwkv_exclusive_lock(iwkv);
|
||||
RCRET(rc);
|
||||
for (khiter_t k = kh_begin(iwkv->dbs); k != kh_end(iwkv->dbs); ++k) {
|
||||
if (!kh_exist(iwkv->dbs, k)) {
|
||||
continue;
|
||||
}
|
||||
uint32_t id = kh_key(iwkv->dbs, k);
|
||||
|
||||
IWHMAP_ITER iter;
|
||||
iwhmap_iter_init(iwkv->dbs, &iter);
|
||||
|
||||
while (iwhmap_iter_next(&iter)) {
|
||||
uint32_t id = (uint32_t) (uintptr_t) iter.key;
|
||||
if (id > dbid) {
|
||||
dbid = id;
|
||||
}
|
||||
}
|
||||
|
||||
dbid++;
|
||||
rc = _db_create_lw(iwkv, dbid, dbflg, dbp);
|
||||
if (!rc) {
|
||||
@@ -3775,18 +3384,6 @@ iwrc iwkv_new_db(IWKV iwkv, iwdb_flags_t dbflg, uint32_t *dbidp, IWDB *dbp) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
iwrc iwkv_db_cache_release(IWDB db) {
|
||||
if (!db || !db->iwkv) {
|
||||
return IW_ERROR_INVALID_ARGS;
|
||||
}
|
||||
int rci;
|
||||
iwrc rc = 0;
|
||||
API_DB_WLOCK(db, rci);
|
||||
_dbcache_destroy_lw(db);
|
||||
API_DB_UNLOCK(db, rci, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
iwrc iwkv_db_destroy(IWDB *dbp) {
|
||||
if (!dbp || !*dbp) {
|
||||
return IW_ERROR_INVALID_ARGS;
|
||||
@@ -3837,13 +3434,7 @@ iwrc iwkv_puth(
|
||||
.phop = phop
|
||||
};
|
||||
API_DB_WLOCK(db, rci);
|
||||
if (!db->cache.open) {
|
||||
rc = _dbcache_fill_lw(&lx);
|
||||
RCGO(rc, finish);
|
||||
}
|
||||
rc = _lx_put_lw(&lx);
|
||||
|
||||
finish:
|
||||
API_DB_UNLOCK(db, rci, rc);
|
||||
if (!rc) {
|
||||
if (lx.opflags & IWKV_SYNC) {
|
||||
@@ -3877,18 +3468,8 @@ iwrc iwkv_get(IWDB db, const IWKV_val *key, IWKV_val *oval) {
|
||||
.nlvl = -1
|
||||
};
|
||||
oval->size = 0;
|
||||
if (IW_LIKELY(db->cache.open)) {
|
||||
API_DB_RLOCK(db, rci);
|
||||
} else {
|
||||
API_DB_WLOCK(db, rci);
|
||||
if (!db->cache.open) { // -V547
|
||||
rc = _dbcache_fill_lw(&lx);
|
||||
RCGO(rc, finish);
|
||||
}
|
||||
}
|
||||
API_DB_RLOCK(db, rci);
|
||||
rc = _lx_get_lr(&lx);
|
||||
|
||||
finish:
|
||||
API_DB_UNLOCK(db, rci, rc);
|
||||
return rc;
|
||||
}
|
||||
@@ -3914,15 +3495,7 @@ iwrc iwkv_get_copy(IWDB db, const IWKV_val *key, void *vbuf, size_t vbufsz, size
|
||||
.key = &ekey,
|
||||
.nlvl = -1
|
||||
};
|
||||
if (IW_LIKELY(db->cache.open)) {
|
||||
API_DB_RLOCK(db, rci);
|
||||
} else {
|
||||
API_DB_WLOCK(db, rci);
|
||||
if (!db->cache.open) { // -V547
|
||||
rc = _dbcache_fill_lw(&lx);
|
||||
RCGO(rc, finish);
|
||||
}
|
||||
}
|
||||
API_DB_RLOCK(db, rci);
|
||||
rc = _lx_find_bounds(&lx);
|
||||
RCGO(rc, finish);
|
||||
rc = fsm->acquire_mmap(fsm, 0, &mm, 0);
|
||||
@@ -4056,13 +3629,7 @@ iwrc iwkv_del(IWDB db, const IWKV_val *key, iwkv_opflags opflags) {
|
||||
.opflags = opflags
|
||||
};
|
||||
API_DB_WLOCK(db, rci);
|
||||
if (!db->cache.open) {
|
||||
rc = _dbcache_fill_lw(&lx);
|
||||
RCGO(rc, finish);
|
||||
}
|
||||
rc = _lx_del_lw(&lx);
|
||||
|
||||
finish:
|
||||
API_DB_UNLOCK(db, rci, rc);
|
||||
if (!rc) {
|
||||
if (lx.opflags & IWKV_SYNC) {
|
||||
@@ -4107,11 +3674,7 @@ iwrc iwkv_cursor_open(
|
||||
int rci;
|
||||
rc = _db_worker_inc_nolk(db);
|
||||
RCRET(rc);
|
||||
if (IW_LIKELY(db->cache.open)) {
|
||||
rc = _api_db_rlock(db);
|
||||
} else {
|
||||
rc = _api_db_wlock(db);
|
||||
}
|
||||
rc = _api_db_rlock(db);
|
||||
if (rc) {
|
||||
_db_worker_dec_nolk(db);
|
||||
return rc;
|
||||
@@ -4131,10 +3694,6 @@ iwrc iwkv_cursor_open(
|
||||
RCGO(rc, finish);
|
||||
lx->key = &lx->ekey;
|
||||
}
|
||||
if (!db->cache.open) {
|
||||
rc = _dbcache_fill_lw(lx);
|
||||
RCGO(rc, finish);
|
||||
}
|
||||
rc = _cursor_to_lr(cur, op);
|
||||
|
||||
finish:
|
||||
@@ -4517,10 +4076,6 @@ iwrc iwkv_cursor_del(IWKV_cursor cur, iwkv_opflags opflags) {
|
||||
IWFS_FSM *fsm = &iwkv->fsm;
|
||||
|
||||
API_DB_WLOCK(db, rci);
|
||||
if (!db->cache.open) {
|
||||
rc = _dbcache_fill_lw(lx);
|
||||
RCGO(rc, finish);
|
||||
}
|
||||
if (sblk->pnum == 1) { // sblk will be removed
|
||||
IWKV_val key = { 0 };
|
||||
// Key a key
|
||||
|
||||
@@ -44,7 +44,6 @@
|
||||
* <strong>Limitations:<strong>
|
||||
* - Maximum iwkv storage file size: 512 GB (0x7fffffff80)
|
||||
* - Total size of a single key+value record must be not greater than 255Mb (0xfffffff)
|
||||
* - In-memory cache for every opened database takes ~130Kb, cache can be disposed by `iwkv_db_cache_release()`
|
||||
*/
|
||||
|
||||
#include "iowow.h"
|
||||
@@ -233,8 +232,6 @@ IW_EXPORT WUR iwrc iwkv_open(const IWKV_OPTS *opts, IWKV *iwkvp);
|
||||
* a new database will be created using specified function arguments.
|
||||
*
|
||||
* @note Database handler doesn't require to be explicitly closed or freed.
|
||||
* Although it may be usefull to release database cache memory of unused databases
|
||||
* dependening on memory requirements of your application by `iwkv_db_cache_release()`.
|
||||
* @note Database `flags` argument must be same for all subsequent
|
||||
* calls after first call for particular database,
|
||||
* otherwise `IWKV_ERROR_INCOMPATIBLE_DB_MODE` will be reported.
|
||||
@@ -256,15 +253,6 @@ IW_EXPORT WUR iwrc iwkv_db(IWKV iwkv, uint32_t dbid, iwdb_flags_t flags, IWDB *d
|
||||
*/
|
||||
IW_EXPORT WUR iwrc iwkv_new_db(IWKV iwkv, iwdb_flags_t dbflg, uint32_t *dbidp, IWDB *dbp);
|
||||
|
||||
/**
|
||||
* @brief Frees memory resources used by database cache
|
||||
* until to next database access operation (get/put/cursor).
|
||||
* Typicaly it will free ~130Kb of memory per database in use.
|
||||
*
|
||||
* @param db Database handler
|
||||
*/
|
||||
IW_EXPORT iwrc iwkv_db_cache_release(IWDB db);
|
||||
|
||||
/**
|
||||
* @brief Destroy(drop) existing database and cleanup all of its data.
|
||||
*
|
||||
|
||||
+4
-49
@@ -9,11 +9,13 @@
|
||||
#include "iwfsmfile.h"
|
||||
#include "iwdlsnr.h"
|
||||
#include "iwal.h"
|
||||
#include "khash.h"
|
||||
#include "iwhmap.h"
|
||||
#include "ksort.h"
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdatomic.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "iwcfg.h"
|
||||
|
||||
#if defined(__APPLE__) || defined(__ANDROID__)
|
||||
@@ -137,10 +139,6 @@ typedef uint8_t sblk_flags_t;
|
||||
#define SBLK_DB ((sblk_flags_t) 0x08U)
|
||||
/** Block data changed, block marked as durty and needs to be persisted */
|
||||
#define SBLK_DURTY ((sblk_flags_t) 0x10U)
|
||||
/** Put this `SBLK` into dbcache */
|
||||
#define SBLK_CACHE_PUT ((sblk_flags_t) 0x20U)
|
||||
#define SBLK_CACHE_UPDATE ((sblk_flags_t) 0x40U)
|
||||
#define SBLK_CACHE_REMOVE ((sblk_flags_t) 0x80U)
|
||||
|
||||
typedef uint8_t iwlctx_op_t;
|
||||
/** Put key value operation */
|
||||
@@ -163,44 +161,6 @@ typedef struct KVBLK {
|
||||
#define SBLK_PERSISTENT_FLAGS (SBLK_FULL_LKEY)
|
||||
#define SBLK_CACHE_FLAGS (SBLK_CACHE_UPDATE | SBLK_CACHE_PUT | SBLK_CACHE_REMOVE)
|
||||
|
||||
// Number of top levels to cache (~ (1<<DBCACHE_LEVELS) cached elements)
|
||||
#define DBCACHE_LEVELS 10U
|
||||
|
||||
// Minimal cached level
|
||||
#define DBCACHE_MIN_LEVEL 5U
|
||||
|
||||
// Single allocation step - number of DBCNODEs
|
||||
#define DBCACHE_ALLOC_STEP 32U
|
||||
|
||||
/** Cached SBLK node */
|
||||
typedef struct DBCNODE {
|
||||
blkn_t sblkn; /**< SBLK block number or used to store key size (to keep DBCNODE compact) */
|
||||
blkn_t kblkn; /**< KVBLK block number */
|
||||
uint8_t lkl; /**< Lower key length */
|
||||
uint8_t fullkey; /**< SBLK is full key */
|
||||
uint8_t k0idx; /**< KVBLK Zero KVP index */
|
||||
uint8_t pad; /**< 1 byte pad */
|
||||
uint8_t lk[1]; /**< Lower key buffer */
|
||||
} DBCNODE;
|
||||
|
||||
#define DBCNODE_VNUM_SZ 24
|
||||
#define DBCNODE_STR_SZ 128
|
||||
|
||||
static_assert(DBCNODE_VNUM_SZ >= offsetof(DBCNODE, lk) + IW_VNUMBUFSZ,
|
||||
"DBCNODE_VNUM_SZ >= offsetof(DBCNODE, lk) + IW_VNUMBUFSZ");
|
||||
static_assert(DBCNODE_STR_SZ >= offsetof(DBCNODE, lk) + SBLK_LKLEN,
|
||||
"DBCNODE_STR_SZ >= offsetof(DBCNODE, lk) + SBLK_LKLEN");
|
||||
|
||||
/** Tallest SBLK nodes cache */
|
||||
typedef struct DBCACHE {
|
||||
size_t asize; /**< Size of allocated cache buffer */
|
||||
size_t num; /**< Actual number of nodes */
|
||||
size_t nsize; /**< Cached node size */
|
||||
uint8_t lvl; /**< Lowes cached level */
|
||||
bool open; /**< Is cache open */
|
||||
DBCNODE *nodes; /**< Sorted nodes array */
|
||||
} DBCACHE;
|
||||
|
||||
struct _IWKV_cursor;
|
||||
|
||||
/* Database: [magic:u4,dbflg:u1,dbid:u4,next_db_blk:u4,p0:u4,n[24]:u4,c[24]:u4]:209 */
|
||||
@@ -211,7 +171,6 @@ struct _IWDB {
|
||||
sblk_flags_t flags; /**< Flags */
|
||||
// !SBH
|
||||
IWKV iwkv;
|
||||
DBCACHE cache; /**< SBLK nodes cache */
|
||||
pthread_rwlock_t rwl; /**< Database API RW lock */
|
||||
pthread_spinlock_t cursors_slk; /**< Cursors set guard lock */
|
||||
off_t next_db_addr; /**< Next IWDB addr */
|
||||
@@ -247,9 +206,6 @@ typedef struct SBLK {
|
||||
uint8_t lk[PREFIX_KEY_LEN_V1]; /**< Lower key buffer */
|
||||
} SBLK;
|
||||
|
||||
// -V:KHASH_MAP_INIT_INT:522
|
||||
KHASH_MAP_INIT_INT(DBS, IWDB)
|
||||
|
||||
/** IWKV instance */
|
||||
struct _IWKV {
|
||||
IWFS_FSM fsm; /**< FSM pool */
|
||||
@@ -258,7 +214,7 @@ struct _IWKV {
|
||||
IWDB first_db; /**< First database in chain */
|
||||
IWDB last_db; /**< Last database in chain */
|
||||
IWDLSNR *dlsnr; /**< WAL data events listener */
|
||||
khash_t(DBS) * dbs; /**< Database id -> IWDB mapping */
|
||||
IWHMAP *dbs; /**< Database id -> IWDB mapping */
|
||||
iwkv_openflags oflags; /**< Open flags */
|
||||
pthread_cond_t wk_cond; /**< Workers cond variable */
|
||||
pthread_mutex_t wk_mtx; /**< Workers cond mutext */
|
||||
@@ -288,7 +244,6 @@ typedef struct IWLCTX {
|
||||
uint8_t saan; /**< Position of next free `SBLK` element in the `saa` area */
|
||||
uint8_t kaan; /**< Position of next free `KVBLK` element in the `kaa` area */
|
||||
int8_t nlvl; /**< Level of new inserted/deleted `SBLK` node. -1 if no new node inserted/deleted */
|
||||
int8_t cache_reload; /**< If true dbcache should be refreshed after operation */
|
||||
IWKV_PUT_HANDLER ph; /**< Optional put handler */
|
||||
void *phop; /**< Put handler opaque data */
|
||||
SBLK *plower[SLEVELS]; /**< Pinned lower nodes per level */
|
||||
|
||||
@@ -398,7 +398,9 @@ static void iwkv_test1_impl(int fmt_version) {
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
rc = iwkv_get(db1, &key, &val);
|
||||
CU_ASSERT_NSTRING_EQUAL(key.data, "foo", key.size);
|
||||
CU_ASSERT_NSTRING_EQUAL(val.data, "", val.size);
|
||||
if (val.data) {
|
||||
CU_ASSERT_NSTRING_EQUAL(val.data, "", val.size);
|
||||
}
|
||||
iwkv_kv_dispose(0, &val);
|
||||
|
||||
logstage(f, "put foo:", db1);
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
check_symbol_exists(basename_r libgen.h HAVE_BASENAME_R)
|
||||
if (HAVE_BASENAME_R)
|
||||
set_source_files_properties(iwlog.c PROPERTIES COMPILE_FLAGS -DIW_HAVE_BASENAME_R)
|
||||
endif()
|
||||
+23
-14
@@ -39,15 +39,6 @@
|
||||
#include <time.h>
|
||||
#include <limits.h>
|
||||
|
||||
#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__ANDROID__) || !_GNU_SOURCE
|
||||
#include <libgen.h>
|
||||
#elif defined(_WIN32)
|
||||
#include <libiberty/libiberty.h>
|
||||
#else
|
||||
#include <string.h>
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#define IW_ANDROID_LOG
|
||||
#include <android/log.h>
|
||||
@@ -289,6 +280,14 @@ static const char* _default_ecodefn(locale_t locale, uint32_t ecode) {
|
||||
return "Action is not allowed. (IW_ERROR_NOT_ALLOWED)";
|
||||
case IW_ERROR_UNSUPPORTED:
|
||||
return "Unsupported opration. (IW_ERROR_UNSUPPORTED)";
|
||||
case IW_ERROR_EOF:
|
||||
return "End of IO stream/file (IW_ERROR_EOF)";
|
||||
case IW_ERROR_UNEXPECTED_INPUT:
|
||||
return "Unexpected input/data (IW_ERROR_UNEXPECTED_INPUT)";
|
||||
case IW_ERROR_IO:
|
||||
return "IO error (IW_ERROR_IO)";
|
||||
case IW_ERROR_INVALID_CONFIG:
|
||||
return "Invalid configuration (IW_ERROR_INVALID_CONFIG)";
|
||||
case IW_OK:
|
||||
default:
|
||||
return 0;
|
||||
@@ -397,6 +396,7 @@ static iwrc _default_logfn(
|
||||
cat = "DEBUG";
|
||||
#endif
|
||||
break;
|
||||
|
||||
case IWLOG_INFO:
|
||||
#ifdef IW_ANDROID_LOG
|
||||
alp = ANDROID_LOG_INFO;
|
||||
@@ -405,6 +405,17 @@ static iwrc _default_logfn(
|
||||
#endif
|
||||
file = 0;
|
||||
break;
|
||||
|
||||
case IWLOG_VERBOSE:
|
||||
#ifdef IW_ANDROID_LOG
|
||||
alp = ANDROID_LOG_INFO;
|
||||
#else
|
||||
cat = "VERBOSE";
|
||||
#endif
|
||||
file = 0;
|
||||
break;
|
||||
|
||||
|
||||
case IWLOG_WARN:
|
||||
#ifdef IW_ANDROID_LOG
|
||||
alp = ANDROID_LOG_WARN;
|
||||
@@ -412,6 +423,7 @@ static iwrc _default_logfn(
|
||||
cat = "WARN";
|
||||
#endif
|
||||
break;
|
||||
|
||||
case IWLOG_ERROR:
|
||||
#ifdef IW_ANDROID_LOG
|
||||
alp = ANDROID_LOG_ERROR;
|
||||
@@ -419,6 +431,7 @@ static iwrc _default_logfn(
|
||||
cat = "ERROR";
|
||||
#endif
|
||||
break;
|
||||
|
||||
default:
|
||||
#ifndef IW_ANDROID_LOG
|
||||
cat = "UNKNOW";
|
||||
@@ -438,11 +451,7 @@ static iwrc _default_logfn(
|
||||
fnameptr = strdup(file);
|
||||
RCA(fnameptr, finish);
|
||||
}
|
||||
#if defined(IW_HAVE_BASENAME_R) && defined(__FreeBSD__)
|
||||
fname = basename_r(file, fnameptr);
|
||||
#else
|
||||
fname = basename(fnameptr); // NOLINT
|
||||
#endif
|
||||
fname = iwp_basename(fnameptr);
|
||||
}
|
||||
|
||||
if (pthread_mutex_lock(&_mtx)) {
|
||||
|
||||
+20
-11
@@ -68,10 +68,6 @@
|
||||
|
||||
IW_EXTERN_C_START
|
||||
|
||||
#ifndef IW_ERROR_START
|
||||
#define IW_ERROR_START 70000
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @enum iw_ecode
|
||||
* @brief Common used error codes.
|
||||
@@ -101,6 +97,10 @@ typedef enum {
|
||||
IW_ERROR_UNEXPECTED_RESPONSE, /**< Unexpected response (IW_ERROR_UNEXPECTED_RESPONSE) */
|
||||
IW_ERROR_NOT_ALLOWED, /**< Action is not allowed. (IW_ERROR_NOT_ALLOWED) */
|
||||
IW_ERROR_UNSUPPORTED, /**< Unsupported opration. (IW_ERROR_UNSUPPORTED) */
|
||||
IW_ERROR_EOF, /**< End of IO stream/file (IW_ERROR_EOF) */
|
||||
IW_ERROR_UNEXPECTED_INPUT, /**< Unexpected input/data (IW_ERROR_UNEXPECTED_INPUT) */
|
||||
IW_ERROR_IO, /**< IO error (IW_ERROR_IO) */
|
||||
IW_ERROR_INVALID_CONFIG, /**< Invalid configuration (IW_ERROR_INVALID_CONFIG) */
|
||||
} iw_ecode;
|
||||
|
||||
/**
|
||||
@@ -108,10 +108,11 @@ typedef enum {
|
||||
* @brief Available logging vebosity levels.
|
||||
*/
|
||||
typedef enum {
|
||||
IWLOG_ERROR = 0,
|
||||
IWLOG_WARN = 1,
|
||||
IWLOG_INFO = 2,
|
||||
IWLOG_DEBUG = 3,
|
||||
IWLOG_ERROR = 0,
|
||||
IWLOG_WARN = 1,
|
||||
IWLOG_INFO = 2,
|
||||
IWLOG_VERBOSE = 3,
|
||||
IWLOG_DEBUG = 4,
|
||||
} iwlog_lvl;
|
||||
|
||||
/**
|
||||
@@ -256,6 +257,8 @@ IW_EXPORT iwrc iwlog_va(
|
||||
#else
|
||||
#define iwlog_debug(IW_fmt, ...)
|
||||
#endif
|
||||
#define iwlog_verbose(IW_fmt, ...) \
|
||||
iwlog2(IWLOG_VERBOSE, 0, __FILE__, __LINE__, (IW_fmt), ## __VA_ARGS__)
|
||||
#define iwlog_info(IW_fmt, ...) \
|
||||
iwlog2(IWLOG_INFO, 0, __FILE__, __LINE__, (IW_fmt), ## __VA_ARGS__)
|
||||
#define iwlog_warn(IW_fmt, ...) \
|
||||
@@ -269,8 +272,9 @@ IW_EXPORT iwrc iwlog_va(
|
||||
#else
|
||||
#define iwlog_debug2(IW_fmt)
|
||||
#endif
|
||||
#define iwlog_info2(IW_fmt) iwlog3(IWLOG_INFO, 0, __FILE__, __LINE__, (IW_fmt))
|
||||
#define iwlog_warn2(IW_fmt) iwlog3(IWLOG_WARN, 0, __FILE__, __LINE__, (IW_fmt))
|
||||
#define iwlog_verbose2(IW_fmt) iwlog3(IWLOG_VERBOSE, 0, __FILE__, __LINE__, (IW_fmt))
|
||||
#define iwlog_info2(IW_fmt) iwlog3(IWLOG_INFO, 0, __FILE__, __LINE__, (IW_fmt))
|
||||
#define iwlog_warn2(IW_fmt) iwlog3(IWLOG_WARN, 0, __FILE__, __LINE__, (IW_fmt))
|
||||
#define iwlog_error2(IW_fmt) \
|
||||
iwlog3(IWLOG_ERROR, 0, __FILE__, __LINE__, (IW_fmt))
|
||||
|
||||
@@ -280,6 +284,8 @@ IW_EXPORT iwrc iwlog_va(
|
||||
#else
|
||||
#define iwlog_ecode_debug(IW_ecode, IW_fmt, ...)
|
||||
#endif
|
||||
#define iwlog_ecode_verbose(IW_ecode, IW_fmt, ...) \
|
||||
iwlog2(IWLOG_VERBOSE, (IW_ecode), __FILE__, __LINE__, (IW_fmt), ## __VA_ARGS__)
|
||||
#define iwlog_ecode_info(IW_ecode, IW_fmt, ...) \
|
||||
iwlog2(IWLOG_INFO, (IW_ecode), __FILE__, __LINE__, (IW_fmt), ## __VA_ARGS__)
|
||||
#define iwlog_ecode_warn(IW_ecode, IW_fmt, ...) \
|
||||
@@ -293,6 +299,8 @@ IW_EXPORT iwrc iwlog_va(
|
||||
#else
|
||||
#define iwlog_ecode_debug2(IW_ecode, IW_fmt)
|
||||
#endif
|
||||
#define iwlog_ecode_verbose2(IW_ecode, IW_fmt) \
|
||||
iwlog3(IWLOG_VERBOSE, (IW_ecode), __FILE__, __LINE__, (IW_fmt))
|
||||
#define iwlog_ecode_info2(IW_ecode, IW_fmt) \
|
||||
iwlog3(IWLOG_INFO, (IW_ecode), __FILE__, __LINE__, (IW_fmt))
|
||||
#define iwlog_ecode_warn2(IW_ecode, IW_fmt) \
|
||||
@@ -306,7 +314,8 @@ IW_EXPORT iwrc iwlog_va(
|
||||
#else
|
||||
#define iwlog_ecode_debug3(IW_ecode)
|
||||
#endif
|
||||
#define iwlog_ecode_info3(IW_ecode) iwlog3(IWLOG_INFO, (IW_ecode), __FILE__, __LINE__, ""))
|
||||
#define iwlog_ecode_verbose3(IW_ecode) iwlog3(IWLOG_VERBOSE, (IW_ecode), __FILE__, __LINE__, ""))
|
||||
#define iwlog_ecode_info3(IW_ecode) iwlog3(IWLOG_INFO, (IW_ecode), __FILE__, __LINE__, ""))
|
||||
#define iwlog_ecode_warn3(IW_ecode) \
|
||||
iwlog3(IWLOG_WARN, (IW_ecode), __FILE__, __LINE__, "")
|
||||
#define iwlog_ecode_error3(IW_ecode) \
|
||||
|
||||
+71
-14
@@ -32,10 +32,15 @@
|
||||
#include "utils/iwuuid.h"
|
||||
#include <stdio.h>
|
||||
|
||||
#if (defined(_WIN32) || defined(__WIN32__))
|
||||
#if defined(_WIN32)
|
||||
#include <libiberty/libiberty.h>
|
||||
#include <direct.h>
|
||||
#else
|
||||
#include <libgen.h>
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
|
||||
unsigned int iwcpuflags = 0;
|
||||
static iwrc _iwp_init_impl(void);
|
||||
|
||||
@@ -49,7 +54,6 @@ static iwrc _iwp_init_impl(void);
|
||||
|
||||
// Thanks to https://attractivechaos.wordpress.com/2017/09/04/on-cpu-dispatch
|
||||
static unsigned int x86_simd(void) {
|
||||
|
||||
#if defined(__i386__) || defined(__amd64__)
|
||||
unsigned int eax, ebx, ecx, edx, flag = 0;
|
||||
# ifdef _MSC_VER
|
||||
@@ -157,32 +161,60 @@ char* iwp_allocate_tmpfile_path(const char *prefix) {
|
||||
return res;
|
||||
}
|
||||
|
||||
char* iwp_dirname(char *path) {
|
||||
return dirname(path);
|
||||
}
|
||||
|
||||
char* iwp_basename(char *path) {
|
||||
size_t i;
|
||||
if (!path || !*path) {
|
||||
return ".";
|
||||
}
|
||||
i = strlen(path) - 1;
|
||||
#ifdef _WIN32
|
||||
for ( ; i && (path[i] == '/' || path[i] == '\\'); i--) path[i] = 0;
|
||||
for ( ; i && (path[i - 1] != '/' && path[i - 1] != '\\'); i--);
|
||||
#else
|
||||
for ( ; i && path[i] == '/'; i--) path[i] = 0;
|
||||
for ( ; i && path[i - 1] != '/'; i--);
|
||||
#endif
|
||||
return path + i;
|
||||
}
|
||||
|
||||
iwrc iwp_mkdirs(const char *path) {
|
||||
/* Adapted from http://stackoverflow.com/a/2336245/119527 */
|
||||
iwrc rc = 0;
|
||||
const size_t len = strlen(path);
|
||||
char _path[PATH_MAX];
|
||||
char *p;
|
||||
char buf[PATH_MAX];
|
||||
char *p, *ppath = buf;
|
||||
|
||||
errno = 0;
|
||||
/* Copy string so its mutable */
|
||||
if (len > sizeof(_path) - 1) {
|
||||
errno = ENAMETOOLONG;
|
||||
return iwrc_set_errno(IW_ERROR_ERRNO, errno);
|
||||
if (len >= sizeof(buf)) {
|
||||
ppath = malloc(len + 1);
|
||||
if (!ppath) {
|
||||
return iwrc_set_errno(IW_ERROR_ALLOC, errno);
|
||||
}
|
||||
}
|
||||
strcpy(_path, path);
|
||||
memcpy(ppath, path, len + 1);
|
||||
|
||||
/* Iterate the string */
|
||||
for (p = _path + 1; *p; p++) {
|
||||
for (p = ppath + 1; *p; p++) {
|
||||
#ifdef _WIN32
|
||||
if (*p == '/' || *p == '\\') {
|
||||
#else
|
||||
if (*p == '/') {
|
||||
#endif
|
||||
/* Temporarily truncate */
|
||||
*p = '\0';
|
||||
#if (defined(_WIN32) || defined(__WIN32__))
|
||||
if (_mkdir(_path) != 0) {
|
||||
#else
|
||||
if (mkdir(_path, S_IRWXU) != 0) {
|
||||
if (mkdir(ppath, S_IRWXU) != 0) {
|
||||
#endif
|
||||
if (errno != EEXIST) {
|
||||
return iwrc_set_errno(IW_ERROR_ERRNO, errno);
|
||||
rc = iwrc_set_errno(IW_ERROR_ERRNO, errno);
|
||||
goto finish;
|
||||
}
|
||||
}
|
||||
*p = '/';
|
||||
@@ -191,13 +223,38 @@ iwrc iwp_mkdirs(const char *path) {
|
||||
#if (defined(_WIN32) || defined(__WIN32__))
|
||||
if (_mkdir(_path) != 0) {
|
||||
#else
|
||||
if (mkdir(_path, S_IRWXU) != 0) {
|
||||
if (mkdir(ppath, S_IRWXU) != 0) {
|
||||
#endif
|
||||
if (errno != EEXIST) {
|
||||
return iwrc_set_errno(IW_ERROR_ERRNO, errno);
|
||||
rc = iwrc_set_errno(IW_ERROR_ERRNO, errno);
|
||||
goto finish;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
finish:
|
||||
if (ppath != buf) {
|
||||
free(ppath);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
iwrc iwp_mkdirs_for_file(const char *path) {
|
||||
char buf[PATH_MAX];
|
||||
char *ppath = buf;
|
||||
const size_t len = strlen(path);
|
||||
if (len >= sizeof(buf)) {
|
||||
ppath = malloc(len + 1);
|
||||
if (!ppath) {
|
||||
return iwrc_set_errno(IW_ERROR_ALLOC, errno);
|
||||
}
|
||||
}
|
||||
memcpy(ppath, path, len + 1);
|
||||
iwp_dirname(ppath);
|
||||
iwrc rc = iwp_mkdirs(ppath);
|
||||
if (ppath != buf) {
|
||||
free(ppath);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
iwrc iwp_init(void) {
|
||||
|
||||
+19
-1
@@ -250,12 +250,30 @@ IW_EXPORT iwrc iwp_removedir(const char *path);
|
||||
*/
|
||||
IW_EXPORT iwrc iwp_mkdirs(const char *path);
|
||||
|
||||
/**
|
||||
* @brief Make directory `dirname(path)` of specified file
|
||||
* as well as all parent directories.
|
||||
*/
|
||||
IW_EXPORT iwrc iwp_mkdirs_for_file(const char *path);
|
||||
|
||||
/**
|
||||
* @brief Platform neutral version of basename.
|
||||
* @note Modifies its `path` argument.
|
||||
*/
|
||||
IW_EXPORT char* iwp_basename(char *path);
|
||||
|
||||
/**
|
||||
* @brief Platform neutral version of dirname.
|
||||
* @note Modifies its `path` argument.
|
||||
*/
|
||||
IW_EXPORT char* iwp_dirname(char *path);
|
||||
|
||||
/**
|
||||
* @brief Get executable path for the current process.
|
||||
* It will be writein into @a opath
|
||||
* @param opath Allocated buffer at least `PATH_MAX` length
|
||||
*/
|
||||
IW_EXPORT iwrc iwp_exec_path(char *opath);
|
||||
IW_EXPORT iwrc iwp_exec_path(char *opath, size_t opath_maxlen);
|
||||
|
||||
/**
|
||||
* @brief Return number of CPU cores.
|
||||
|
||||
+47
-18
@@ -43,15 +43,18 @@
|
||||
#include <sys/prctl.h>
|
||||
#elif defined(__FreeBSD__) || defined(__DragonFly__) || defined(__OpenBSD__)
|
||||
#include <pthread_np.h>
|
||||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <libproc.h>
|
||||
|
||||
#define st_atim st_atimespec
|
||||
#define st_ctim st_ctimespec
|
||||
#define st_mtim st_mtimespec
|
||||
#endif
|
||||
|
||||
#define _IW_TIMESPEC2MS(IW_ts) (((IW_ts).tv_sec * 1000ULL) + (uint64_t) round((IW_ts).tv_nsec / 1.0e6))
|
||||
#define _IW_TIMESPEC2MS(IW_ts) (((IW_ts).tv_sec * 1000ULL) + lround((IW_ts).tv_nsec / 1.0e6))
|
||||
|
||||
IW_EXPORT iwrc iwp_clock_get_time(int clock_id, struct timespec *t) {
|
||||
#if (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED < 101200)
|
||||
@@ -296,7 +299,11 @@ iwrc iwp_sleep(uint64_t ms) {
|
||||
struct timespec req;
|
||||
req.tv_sec = ms / 1000UL;
|
||||
req.tv_nsec = (ms % 1000UL) * 1000UL * 1000UL;
|
||||
if (nanosleep(&req, NULL)) {
|
||||
again:
|
||||
if (nanosleep(&req, NULL) == -1) {
|
||||
if (errno == EINTR) {
|
||||
goto again;
|
||||
}
|
||||
rc = iwrc_set_errno(IW_ERROR_THREADING_ERRNO, errno);
|
||||
}
|
||||
return rc;
|
||||
@@ -317,27 +324,37 @@ iwrc iwp_removedir(const char *path) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
iwrc iwp_exec_path(char *opath) {
|
||||
#ifdef __linux
|
||||
pid_t pid;
|
||||
char path[MAXPATHLEN];
|
||||
char epath[MAXPATHLEN];
|
||||
memset(epath, 0, sizeof(epath));
|
||||
pid = getpid();
|
||||
sprintf(path, "/proc/%d/exe", pid);
|
||||
if (readlink(path, epath, MAXPATHLEN - 1) == -1) {
|
||||
iwrc iwp_exec_path(char *opath, size_t opath_maxlen) {
|
||||
#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__DragonFly__)
|
||||
const int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };
|
||||
if (sysctl(mib, 4, opath, &opath_maxlen, 0, 0) < 0) {
|
||||
return iwrc_set_errno(IW_ERROR_ERRNO, errno);
|
||||
} else {
|
||||
strncpy(opath, epath, MAXPATHLEN);
|
||||
}
|
||||
return 0;
|
||||
#elif defined(__linux__)
|
||||
char *path = "/proc/self/exe";
|
||||
ssize_t ret = readlink(path, opath, opath_maxlen);
|
||||
if (ret == -1) {
|
||||
return iwrc_set_errno(IW_ERROR_ERRNO, errno);
|
||||
} else if (ret < opath_maxlen) {
|
||||
opath[ret] = '\0';
|
||||
} else if (opath_maxlen > 0) {
|
||||
opath[opath_maxlen - 1] = '\0';
|
||||
}
|
||||
return 0;
|
||||
#elif defined(__APPLE__)
|
||||
pid_t pid = getpid();
|
||||
int ret = proc_pidpath(pid, opath, opath_maxlen);
|
||||
if (ret < 0) {
|
||||
return iwrc_set_errno(IW_ERROR_ERRNO, errno);
|
||||
}
|
||||
#else
|
||||
// todo
|
||||
// TODO:
|
||||
return IW_ERROR_NOT_IMPLEMENTED;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint16_t iwp_num_cpu_cores() {
|
||||
uint16_t iwp_num_cpu_cores(void) {
|
||||
long res = sysconf(_SC_NPROCESSORS_ONLN);
|
||||
return (uint16_t) (res > 0 ? res : 1);
|
||||
}
|
||||
@@ -361,8 +378,20 @@ iwrc iwp_fdatasync(HANDLE fh) {
|
||||
}
|
||||
|
||||
size_t iwp_tmpdir(char *out, size_t len) {
|
||||
const char *tdir = P_tmpdir;
|
||||
size_t tlen = strlen(P_tmpdir);
|
||||
const char *tdir;
|
||||
#ifdef IW_TMPDIR
|
||||
tdir = IW_TMPDIR;
|
||||
#else
|
||||
tdir = getenv("TMPDIR");
|
||||
if (!tdir) {
|
||||
#ifdef P_tmpdir
|
||||
tdir = P_tmpdir;
|
||||
#else
|
||||
tdir = "/tmp";
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
size_t tlen = strlen(tdir);
|
||||
size_t nw = MIN(len, tlen);
|
||||
memcpy(out, tdir, nw);
|
||||
return nw;
|
||||
@@ -389,7 +418,7 @@ void iwp_set_current_thread_name(const char *name) {
|
||||
#endif
|
||||
}
|
||||
|
||||
static iwrc _iwp_init_impl() {
|
||||
static iwrc _iwp_init_impl(void) {
|
||||
iwp_page_size(); // init statics
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ iwrc iwp_fdatasync(HANDLE fh) {
|
||||
|
||||
static SYSTEM_INFO sysinfo;
|
||||
|
||||
static void _iwp_getsysinfo() {
|
||||
static void _iwp_getsysinfo(void) {
|
||||
GetSystemInfo(&sysinfo);
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ size_t iwp_alloc_unit(void) {
|
||||
return sysinfo.dwAllocationGranularity;
|
||||
}
|
||||
|
||||
uint16_t iwp_num_cpu_cores() {
|
||||
uint16_t iwp_num_cpu_cores(void) {
|
||||
return sysinfo.dwNumberOfProcessors;
|
||||
}
|
||||
|
||||
@@ -314,7 +314,7 @@ size_t iwp_tmpdir(char *out, size_t len) {
|
||||
return GetTempPathA(len, out);
|
||||
}
|
||||
|
||||
static iwrc _iwp_init_impl() {
|
||||
static iwrc _iwp_init_impl(void) {
|
||||
_iwp_getsysinfo();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Based on https://github.com/jserv/cregex library (BSD 2-Clause "Simplified" License)
|
||||
@@ -0,0 +1,339 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "cregex.h"
|
||||
|
||||
typedef struct {
|
||||
cregex_program_instr_t *pc;
|
||||
int ncaptures;
|
||||
} regex_compile_context;
|
||||
|
||||
static int count_instructions(const cregex_node_t *node) {
|
||||
switch (node->type) {
|
||||
case REGEX_NODE_TYPE_EPSILON:
|
||||
return 0;
|
||||
|
||||
/* Characters */
|
||||
case REGEX_NODE_TYPE_CHARACTER:
|
||||
case REGEX_NODE_TYPE_ANY_CHARACTER:
|
||||
case REGEX_NODE_TYPE_CHARACTER_CLASS:
|
||||
case REGEX_NODE_TYPE_CHARACTER_CLASS_NEGATED:
|
||||
return 1;
|
||||
|
||||
/* Composites */
|
||||
case REGEX_NODE_TYPE_CONCATENATION:
|
||||
return count_instructions(node->left) + count_instructions(node->right);
|
||||
case REGEX_NODE_TYPE_ALTERNATION:
|
||||
return 2 + count_instructions(node->left)
|
||||
+ count_instructions(node->right);
|
||||
|
||||
/* Quantifiers */
|
||||
case REGEX_NODE_TYPE_QUANTIFIER: {
|
||||
int num = count_instructions(node->quantified);
|
||||
if (node->nmax >= node->nmin) {
|
||||
return node->nmin * num + (node->nmax - node->nmin) * (num + 1);
|
||||
}
|
||||
return 1 + (node->nmin ? node->nmin * num : num + 1);
|
||||
}
|
||||
|
||||
/* Anchors */
|
||||
case REGEX_NODE_TYPE_ANCHOR_BEGIN:
|
||||
case REGEX_NODE_TYPE_ANCHOR_END:
|
||||
return 1;
|
||||
|
||||
/* Captures */
|
||||
case REGEX_NODE_TYPE_CAPTURE:
|
||||
return 2 + count_instructions(node->captured);
|
||||
}
|
||||
|
||||
/* should not reach here */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool node_is_anchored(const cregex_node_t *node) {
|
||||
switch (node->type) {
|
||||
case REGEX_NODE_TYPE_EPSILON:
|
||||
return false;
|
||||
|
||||
/* Characters */
|
||||
case REGEX_NODE_TYPE_CHARACTER:
|
||||
case REGEX_NODE_TYPE_ANY_CHARACTER:
|
||||
case REGEX_NODE_TYPE_CHARACTER_CLASS:
|
||||
case REGEX_NODE_TYPE_CHARACTER_CLASS_NEGATED:
|
||||
return false;
|
||||
|
||||
/* Composites */
|
||||
case REGEX_NODE_TYPE_CONCATENATION:
|
||||
return node_is_anchored(node->left);
|
||||
case REGEX_NODE_TYPE_ALTERNATION:
|
||||
return node_is_anchored(node->left) && node_is_anchored(node->right);
|
||||
|
||||
/* Quantifiers */
|
||||
case REGEX_NODE_TYPE_QUANTIFIER:
|
||||
return node_is_anchored(node->quantified);
|
||||
|
||||
/* Anchors */
|
||||
case REGEX_NODE_TYPE_ANCHOR_BEGIN:
|
||||
return true;
|
||||
case REGEX_NODE_TYPE_ANCHOR_END:
|
||||
return false;
|
||||
|
||||
/* Captures */
|
||||
case REGEX_NODE_TYPE_CAPTURE:
|
||||
return node_is_anchored(node->captured);
|
||||
}
|
||||
|
||||
/* should not reach here */
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline cregex_program_instr_t* emit(
|
||||
regex_compile_context *context,
|
||||
const cregex_program_instr_t *instruction
|
||||
) {
|
||||
*context->pc = *instruction;
|
||||
return context->pc++;
|
||||
}
|
||||
|
||||
static cregex_program_instr_t* compile_char_class(
|
||||
const cregex_node_t *node,
|
||||
cregex_program_instr_t *instruction
|
||||
) {
|
||||
const char *sp = node->from;
|
||||
|
||||
for ( ; ; ) {
|
||||
int ch = *sp++;
|
||||
switch (ch) {
|
||||
case ']':
|
||||
if (sp - 1 == node->from) {
|
||||
goto CHARACTER;
|
||||
}
|
||||
return instruction;
|
||||
case '\\':
|
||||
ch = *sp++;
|
||||
/* fall-through */
|
||||
default:
|
||||
CHARACTER:
|
||||
if (*sp == '-' && sp[1] != ']') {
|
||||
for ( ; ch <= sp[1]; ++ch) {
|
||||
cregex_char_class_add(instruction->klass, ch);
|
||||
}
|
||||
sp += 2;
|
||||
} else {
|
||||
cregex_char_class_add(instruction->klass, ch);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static cregex_program_instr_t* compile_context(
|
||||
regex_compile_context *context,
|
||||
const cregex_node_t *node
|
||||
) {
|
||||
cregex_program_instr_t *bottom = context->pc, *split, *jump;
|
||||
int ncaptures = context->ncaptures, capture;
|
||||
|
||||
switch (node->type) {
|
||||
case REGEX_NODE_TYPE_EPSILON:
|
||||
break;
|
||||
|
||||
/* Characters */
|
||||
case REGEX_NODE_TYPE_CHARACTER:
|
||||
emit(context,
|
||||
&(cregex_program_instr_t) { .opcode = REGEX_PROGRAM_OPCODE_CHARACTER,
|
||||
.ch = node->ch });
|
||||
break;
|
||||
case REGEX_NODE_TYPE_ANY_CHARACTER:
|
||||
emit(context, &(cregex_program_instr_t) {
|
||||
.opcode = REGEX_PROGRAM_OPCODE_ANY_CHARACTER
|
||||
});
|
||||
break;
|
||||
case REGEX_NODE_TYPE_CHARACTER_CLASS:
|
||||
compile_char_class(
|
||||
node,
|
||||
emit(context, &(cregex_program_instr_t) {
|
||||
.opcode = REGEX_PROGRAM_OPCODE_CHARACTER_CLASS
|
||||
}));
|
||||
break;
|
||||
case REGEX_NODE_TYPE_CHARACTER_CLASS_NEGATED:
|
||||
compile_char_class(
|
||||
node,
|
||||
emit(context,
|
||||
&(cregex_program_instr_t) {
|
||||
.opcode = REGEX_PROGRAM_OPCODE_CHARACTER_CLASS_NEGATED
|
||||
}));
|
||||
break;
|
||||
|
||||
/* Composites */
|
||||
case REGEX_NODE_TYPE_CONCATENATION:
|
||||
compile_context(context, node->left);
|
||||
compile_context(context, node->right);
|
||||
break;
|
||||
case REGEX_NODE_TYPE_ALTERNATION:
|
||||
split = emit(context, &(cregex_program_instr_t) {
|
||||
.opcode = REGEX_PROGRAM_OPCODE_SPLIT
|
||||
});
|
||||
split->first = compile_context(context, node->left);
|
||||
jump = emit(context, &(cregex_program_instr_t) {
|
||||
.opcode = REGEX_PROGRAM_OPCODE_JUMP
|
||||
});
|
||||
split->second = compile_context(context, node->right);
|
||||
jump->target = context->pc;
|
||||
break;
|
||||
|
||||
/* Quantifiers */
|
||||
case REGEX_NODE_TYPE_QUANTIFIER: {
|
||||
cregex_program_instr_t *last = NULL;
|
||||
for (int i = 0; i < node->nmin; ++i) {
|
||||
context->ncaptures = ncaptures;
|
||||
last = compile_context(context, node->quantified);
|
||||
}
|
||||
if (node->nmax > node->nmin) {
|
||||
for (int i = 0; i < node->nmax - node->nmin; ++i) {
|
||||
context->ncaptures = ncaptures;
|
||||
split
|
||||
= emit(context, &(cregex_program_instr_t) {
|
||||
.opcode = REGEX_PROGRAM_OPCODE_SPLIT
|
||||
});
|
||||
split->first = compile_context(context, node->quantified);
|
||||
split->second = context->pc;
|
||||
if (!node->greedy) {
|
||||
cregex_program_instr_t *swap = split->first;
|
||||
split->first = split->second;
|
||||
split->second = swap;
|
||||
}
|
||||
}
|
||||
} else if (node->nmax == -1) {
|
||||
split = emit(context, &(cregex_program_instr_t) {
|
||||
.opcode = REGEX_PROGRAM_OPCODE_SPLIT
|
||||
});
|
||||
if (node->nmin == 0) {
|
||||
split->first = compile_context(context, node->quantified);
|
||||
jump = emit(context, &(cregex_program_instr_t) {
|
||||
.opcode = REGEX_PROGRAM_OPCODE_JUMP
|
||||
});
|
||||
split->second = context->pc;
|
||||
jump->target = split;
|
||||
} else {
|
||||
split->first = last;
|
||||
split->second = context->pc;
|
||||
}
|
||||
if (!node->greedy) {
|
||||
cregex_program_instr_t *swap = split->first;
|
||||
split->first = split->second;
|
||||
split->second = swap;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Anchors */
|
||||
case REGEX_NODE_TYPE_ANCHOR_BEGIN:
|
||||
emit(context, &(cregex_program_instr_t) {
|
||||
.opcode = REGEX_PROGRAM_OPCODE_ASSERT_BEGIN
|
||||
});
|
||||
break;
|
||||
case REGEX_NODE_TYPE_ANCHOR_END:
|
||||
emit(context, &(cregex_program_instr_t) {
|
||||
.opcode = REGEX_PROGRAM_OPCODE_ASSERT_END
|
||||
});
|
||||
break;
|
||||
|
||||
/* Captures */
|
||||
case REGEX_NODE_TYPE_CAPTURE:
|
||||
capture = context->ncaptures++ *2;
|
||||
emit(context,
|
||||
&(cregex_program_instr_t) { .opcode = REGEX_PROGRAM_OPCODE_SAVE,
|
||||
.save = capture });
|
||||
compile_context(context, node->captured);
|
||||
emit(context,
|
||||
&(cregex_program_instr_t) { .opcode = REGEX_PROGRAM_OPCODE_SAVE,
|
||||
.save = capture + 1 });
|
||||
break;
|
||||
}
|
||||
|
||||
return bottom;
|
||||
}
|
||||
|
||||
/* Compile a parsed pattern (using a previously allocated program with at least
|
||||
* estimate_instructions(root) instructions).
|
||||
*/
|
||||
static cregex_program_t* compile_node_with_program(
|
||||
const cregex_node_t *root,
|
||||
cregex_program_t *program
|
||||
) {
|
||||
/* add capture node for entire match */
|
||||
root = &(cregex_node_t) {
|
||||
.type = REGEX_NODE_TYPE_CAPTURE,
|
||||
.captured = (cregex_node_t*) root
|
||||
};
|
||||
|
||||
cregex_node_t naroot = (cregex_node_t) {
|
||||
.type = REGEX_NODE_TYPE_CONCATENATION,
|
||||
.left
|
||||
= &(cregex_node_t) {
|
||||
.type = REGEX_NODE_TYPE_QUANTIFIER,
|
||||
.nmin = 0,
|
||||
.nmax = -1,
|
||||
.greedy = 0,
|
||||
.quantified = &(
|
||||
cregex_node_t) {
|
||||
.type = REGEX_NODE_TYPE_ANY_CHARACTER
|
||||
}
|
||||
},
|
||||
.right = (cregex_node_t*) root
|
||||
};
|
||||
|
||||
/* add .*? unless pattern starts with ^ */
|
||||
if (!node_is_anchored(root)) {
|
||||
root = &naroot;
|
||||
}
|
||||
|
||||
/* compile */
|
||||
regex_compile_context *context
|
||||
= &(regex_compile_context) {
|
||||
.pc = program->instructions, .ncaptures = 0
|
||||
};
|
||||
compile_context(context, root);
|
||||
|
||||
/* emit final match instruction */
|
||||
emit(context,
|
||||
&(cregex_program_instr_t) { .opcode = REGEX_PROGRAM_OPCODE_MATCH });
|
||||
|
||||
/* set total number of instructions */
|
||||
program->ninstructions = context->pc - program->instructions;
|
||||
return program;
|
||||
}
|
||||
|
||||
/* Upper bound of number of instructions required to compile parsed pattern. */
|
||||
static int estimate_instructions(const cregex_node_t *root) {
|
||||
return count_instructions(root)
|
||||
/* .*? is added unless pattern starts with ^,
|
||||
* save instructions are added for beginning and end of match,
|
||||
* a final match instruction is added to the end of the program
|
||||
*/
|
||||
+ !node_is_anchored(root) * 3 + 2 + 1;
|
||||
}
|
||||
|
||||
cregex_program_t* cregex_compile_node(const cregex_node_t *root) {
|
||||
size_t size = sizeof(cregex_program_t)
|
||||
+ sizeof(cregex_program_instr_t) * estimate_instructions(root);
|
||||
cregex_program_t *program;
|
||||
|
||||
if (!(program = malloc(size))) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!compile_node_with_program(root, program)) {
|
||||
free(program);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
/* Free a compiled program */
|
||||
void cregex_compile_free(cregex_program_t *program) {
|
||||
free(program);
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
#ifndef CREGEX_H
|
||||
#define CREGEX_H
|
||||
|
||||
typedef enum {
|
||||
REGEX_NODE_TYPE_EPSILON = 0,
|
||||
/* Characters */
|
||||
REGEX_NODE_TYPE_CHARACTER,
|
||||
REGEX_NODE_TYPE_ANY_CHARACTER,
|
||||
REGEX_NODE_TYPE_CHARACTER_CLASS,
|
||||
REGEX_NODE_TYPE_CHARACTER_CLASS_NEGATED,
|
||||
/* Composites */
|
||||
REGEX_NODE_TYPE_CONCATENATION,
|
||||
REGEX_NODE_TYPE_ALTERNATION,
|
||||
/* Quantifiers */
|
||||
REGEX_NODE_TYPE_QUANTIFIER,
|
||||
/* Anchors */
|
||||
REGEX_NODE_TYPE_ANCHOR_BEGIN,
|
||||
REGEX_NODE_TYPE_ANCHOR_END,
|
||||
/* Captures */
|
||||
REGEX_NODE_TYPE_CAPTURE,
|
||||
} cregex_node_type;
|
||||
|
||||
typedef struct cregex_node {
|
||||
cregex_node_type type;
|
||||
union {
|
||||
/* REGEX_NODE_TYPE_CHARACTER */
|
||||
struct {
|
||||
int ch;
|
||||
};
|
||||
/* REGEX_NODE_TYPE_CHARACTER_CLASS,
|
||||
* REGEX_NODE_TYPE_CHARACTER_CLASS_NEGATED
|
||||
*/
|
||||
struct {
|
||||
const char *from, *to;
|
||||
};
|
||||
/* REGEX_NODE_TYPE_QUANTIFIER */
|
||||
struct {
|
||||
int nmin, nmax, greedy;
|
||||
struct cregex_node *quantified;
|
||||
};
|
||||
/* REGEX_NODE_TYPE_CONCATENATION,
|
||||
* REGEX_NODE_TYPE_ALTERNATION
|
||||
*/
|
||||
struct {
|
||||
struct cregex_node *left, *right;
|
||||
};
|
||||
/* REGEX_NODE_TYPE_CAPTURE */
|
||||
struct {
|
||||
struct cregex_node *captured;
|
||||
};
|
||||
};
|
||||
} cregex_node_t;
|
||||
|
||||
typedef enum {
|
||||
REGEX_PROGRAM_OPCODE_MATCH = 0,
|
||||
/* Characters */
|
||||
REGEX_PROGRAM_OPCODE_CHARACTER,
|
||||
REGEX_PROGRAM_OPCODE_ANY_CHARACTER,
|
||||
REGEX_PROGRAM_OPCODE_CHARACTER_CLASS,
|
||||
REGEX_PROGRAM_OPCODE_CHARACTER_CLASS_NEGATED,
|
||||
/* Control-flow */
|
||||
REGEX_PROGRAM_OPCODE_SPLIT,
|
||||
REGEX_PROGRAM_OPCODE_JUMP,
|
||||
/* Assertions */
|
||||
REGEX_PROGRAM_OPCODE_ASSERT_BEGIN,
|
||||
REGEX_PROGRAM_OPCODE_ASSERT_END,
|
||||
/* Saving */
|
||||
REGEX_PROGRAM_OPCODE_SAVE,
|
||||
} cregex_program_opcode_t;
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
typedef char cregex_char_class[(UCHAR_MAX + CHAR_BIT - 1) / CHAR_BIT];
|
||||
|
||||
static inline int cregex_char_class_contains(
|
||||
const cregex_char_class klass,
|
||||
int ch
|
||||
) {
|
||||
return klass[ch / CHAR_BIT] & (1 << ch % CHAR_BIT);
|
||||
}
|
||||
|
||||
static inline int cregex_char_class_add(cregex_char_class klass, int ch) {
|
||||
klass[ch / CHAR_BIT] |= 1 << (ch % CHAR_BIT);
|
||||
return ch;
|
||||
}
|
||||
|
||||
typedef struct cregex_program_instr {
|
||||
cregex_program_opcode_t opcode;
|
||||
union {
|
||||
/* REGEX_PROGRAM_OPCODE_CHARACTER */
|
||||
struct {
|
||||
int ch;
|
||||
};
|
||||
/* REGEX_PROGRAM_OPCODE_CHARACTER_CLASS,
|
||||
* REGEX_PROGRAM_OPCODE_CHARACTER_CLASS_NEGATED
|
||||
*/
|
||||
struct {
|
||||
cregex_char_class klass;
|
||||
};
|
||||
/* REGEX_PROGRAM_OPCODE_SPLIT */
|
||||
struct {
|
||||
struct cregex_program_instr *first, *second;
|
||||
};
|
||||
/* REGEX_PROGRAM_OPCODE_JUMP */
|
||||
struct {
|
||||
struct cregex_program_instr *target;
|
||||
};
|
||||
/* REGEX_PROGRAM_OPCODE_SAVE */
|
||||
struct {
|
||||
int save;
|
||||
};
|
||||
};
|
||||
} cregex_program_instr_t;
|
||||
|
||||
typedef struct {
|
||||
int ninstructions;
|
||||
cregex_program_instr_t instructions[];
|
||||
} cregex_program_t;
|
||||
|
||||
/* Run program on string */
|
||||
int cregex_program_run(
|
||||
const cregex_program_t *program,
|
||||
const char *string,
|
||||
const char **matches,
|
||||
int nmatches);
|
||||
|
||||
/* Compile a parsed pattern */
|
||||
cregex_program_t* cregex_compile_node(const cregex_node_t *root);
|
||||
|
||||
/* Free a compiled program */
|
||||
void cregex_compile_free(cregex_program_t *program);
|
||||
|
||||
/* Parse a pattern */
|
||||
cregex_node_t* cregex_parse(const char *pattern);
|
||||
|
||||
/* Free a parsed pattern */
|
||||
void cregex_parse_free(cregex_node_t *root);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,64 @@
|
||||
#include "iwre.h"
|
||||
#include "cregex.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
struct iwre {
|
||||
const char *pattern;
|
||||
cregex_program_t *program;
|
||||
};
|
||||
|
||||
const char* iwre_pattern_get(struct iwre *re) {
|
||||
return re->pattern;
|
||||
}
|
||||
|
||||
int iwre_match(struct iwre *re, const char *text, const char *mpairs[], size_t mpairs_len) {
|
||||
if (mpairs_len % 2 != 0) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
memset(mpairs, 0, sizeof(mpairs[0]) * mpairs_len);
|
||||
int ret = cregex_program_run(re->program, text, mpairs, mpairs_len);
|
||||
if (ret < 1) {
|
||||
return 0;
|
||||
}
|
||||
ret = 0;
|
||||
for (int i = 0; i < mpairs_len && mpairs[i]; ++i) {
|
||||
++ret;
|
||||
}
|
||||
return ret / 2;
|
||||
}
|
||||
|
||||
void iwre_destroy(struct iwre *re) {
|
||||
if (re) {
|
||||
cregex_compile_free(re->program);
|
||||
free(re);
|
||||
}
|
||||
}
|
||||
|
||||
struct iwre* iwre_create(const char *pattern) {
|
||||
struct iwre *re = calloc(1, sizeof(*re));
|
||||
if (!re) {
|
||||
return 0;
|
||||
}
|
||||
cregex_node_t *node = cregex_parse(pattern);
|
||||
if (!node) {
|
||||
goto error;
|
||||
}
|
||||
re->pattern = pattern;
|
||||
re->program = cregex_compile_node(node);
|
||||
if (!re->program) {
|
||||
goto error;
|
||||
}
|
||||
cregex_parse_free(node);
|
||||
return re;
|
||||
|
||||
error:
|
||||
if (node) {
|
||||
cregex_parse_free(node);
|
||||
}
|
||||
iwre_destroy(re);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef IW_IWRE_H
|
||||
#define IW_IWRE_H
|
||||
|
||||
#include "basedefs.h"
|
||||
|
||||
#define IWRE_MAX_MATCHES 64
|
||||
|
||||
struct iwre;
|
||||
|
||||
struct iwre* iwre_create(const char *pattern);
|
||||
|
||||
const char* iwre_pattern_get(struct iwre*);
|
||||
|
||||
/// @return Number of of matches `n`, where `2*n <= nmatches`
|
||||
int iwre_match(struct iwre*, const char *text, const char *mpairs[], size_t mpairs_len);
|
||||
|
||||
void iwre_destroy(struct iwre*);
|
||||
|
||||
#endif
|
||||
+294
@@ -0,0 +1,294 @@
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "cregex.h"
|
||||
|
||||
typedef struct {
|
||||
const char *sp;
|
||||
cregex_node_t *stack, *output;
|
||||
} regex_parse_context;
|
||||
|
||||
/* Shunting-yard algorithm
|
||||
* See https://en.wikipedia.org/wiki/Shunting-yard_algorithm
|
||||
*/
|
||||
|
||||
static inline cregex_node_t* push(
|
||||
regex_parse_context *context,
|
||||
const cregex_node_t *node
|
||||
) {
|
||||
assert(context->stack <= context->output);
|
||||
*context->stack = *node;
|
||||
return context->stack++;
|
||||
}
|
||||
|
||||
static inline cregex_node_t* drop(regex_parse_context *context) {
|
||||
return --context->stack;
|
||||
}
|
||||
|
||||
static inline cregex_node_t* consume(regex_parse_context *context) {
|
||||
*--context->output = *--context->stack;
|
||||
return context->output;
|
||||
}
|
||||
|
||||
static inline cregex_node_t* concatenate(
|
||||
regex_parse_context *context,
|
||||
const cregex_node_t *bottom
|
||||
) {
|
||||
if (context->stack == bottom) {
|
||||
push(context, &(cregex_node_t) { .type = REGEX_NODE_TYPE_EPSILON });
|
||||
} else {
|
||||
while (context->stack - 1 > bottom) {
|
||||
cregex_node_t *right = consume(context);
|
||||
cregex_node_t *left = consume(context);
|
||||
push(context,
|
||||
&(cregex_node_t) { .type = REGEX_NODE_TYPE_CONCATENATION,
|
||||
.left = left,
|
||||
.right = right });
|
||||
}
|
||||
}
|
||||
return context->stack - 1;
|
||||
}
|
||||
|
||||
static cregex_node_t* parse_char_class(regex_parse_context *context) {
|
||||
cregex_node_type type
|
||||
= (*context->sp == '^')
|
||||
? (++context->sp, REGEX_NODE_TYPE_CHARACTER_CLASS_NEGATED)
|
||||
: REGEX_NODE_TYPE_CHARACTER_CLASS;
|
||||
const char *from = context->sp;
|
||||
|
||||
for ( ; ; ) {
|
||||
int ch = *context->sp++;
|
||||
switch (ch) {
|
||||
case '\0':
|
||||
/* premature end of character class */
|
||||
return NULL;
|
||||
case ']':
|
||||
if (context->sp - 1 == from) {
|
||||
goto CHARACTER;
|
||||
}
|
||||
return push(context,
|
||||
&(cregex_node_t) {
|
||||
.type = type, .from = from, .to = context->sp - 1
|
||||
});
|
||||
case '\\':
|
||||
ch = *context->sp++;
|
||||
/* fall-through */
|
||||
default:
|
||||
CHARACTER:
|
||||
if (*context->sp == '-' && context->sp[1] != ']') {
|
||||
if (context->sp[1] < ch) {
|
||||
/* empty range in character class */
|
||||
return NULL;
|
||||
}
|
||||
context->sp += 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static cregex_node_t* parse_interval(regex_parse_context *context) {
|
||||
const char *from = context->sp;
|
||||
int nmin, nmax;
|
||||
|
||||
for (nmin = 0; *context->sp >= '0' && *context->sp <= '9'; ++context->sp)
|
||||
nmin = (nmin * 10) + (*context->sp - '0');
|
||||
|
||||
if (*context->sp == ',') {
|
||||
++context->sp;
|
||||
if (*from != ',' && *context->sp == '}') {
|
||||
nmax = -1;
|
||||
} else {
|
||||
for (nmax = 0; *context->sp >= '0' && *context->sp <= '9';
|
||||
++context->sp)
|
||||
nmax = (nmax * 10) + (*context->sp - '0');
|
||||
if ( *(context->sp - 1) == ',' || *context->sp != '}'
|
||||
|| nmax < nmin) {
|
||||
context->sp = from;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
} else if (*from != '}' && *context->sp == '}') {
|
||||
nmax = nmin;
|
||||
} else {
|
||||
context->sp = from;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
++context->sp;
|
||||
return push(context,
|
||||
&(cregex_node_t) {
|
||||
.type = REGEX_NODE_TYPE_QUANTIFIER,
|
||||
.nmin = nmin,
|
||||
.nmax = nmax,
|
||||
.greedy = (*context->sp == '?') ? (++context->sp, 0) : 1,
|
||||
.quantified = consume(context)
|
||||
});
|
||||
}
|
||||
|
||||
static cregex_node_t* parse_context(regex_parse_context *context, int depth) {
|
||||
cregex_node_t *bottom = context->stack;
|
||||
|
||||
for ( ; ; ) {
|
||||
int ch = *context->sp++;
|
||||
switch (ch) {
|
||||
/* Characters */
|
||||
case '\\':
|
||||
ch = *context->sp++;
|
||||
/* fall-through */
|
||||
default:
|
||||
CHARACTER:
|
||||
push(context,
|
||||
&(cregex_node_t) { .type = REGEX_NODE_TYPE_CHARACTER, .ch = ch });
|
||||
break;
|
||||
case '.':
|
||||
push(context,
|
||||
&(cregex_node_t) { .type = REGEX_NODE_TYPE_ANY_CHARACTER });
|
||||
break;
|
||||
case '[':
|
||||
if (!parse_char_class(context)) {
|
||||
return NULL;
|
||||
}
|
||||
break;
|
||||
|
||||
/* Composites */
|
||||
case '|': {
|
||||
cregex_node_t *left = concatenate(context, bottom), *right;
|
||||
if (!(right = parse_context(context, depth))) {
|
||||
return NULL;
|
||||
}
|
||||
if ( left->type == REGEX_NODE_TYPE_EPSILON
|
||||
&& right->type == left->type) {
|
||||
drop(context);
|
||||
} else if (left->type == REGEX_NODE_TYPE_EPSILON) {
|
||||
right = consume(context);
|
||||
drop(context);
|
||||
push(context,
|
||||
&(cregex_node_t) { .type = REGEX_NODE_TYPE_QUANTIFIER,
|
||||
.nmin = 0,
|
||||
.nmax = 1,
|
||||
.greedy = 1,
|
||||
.quantified = right });
|
||||
} else if (right->type == REGEX_NODE_TYPE_EPSILON) {
|
||||
drop(context);
|
||||
left = consume(context);
|
||||
push(context,
|
||||
&(cregex_node_t) { .type = REGEX_NODE_TYPE_QUANTIFIER,
|
||||
.nmin = 0,
|
||||
.nmax = 1,
|
||||
.greedy = 1,
|
||||
.quantified = left });
|
||||
} else {
|
||||
right = consume(context);
|
||||
left = consume(context);
|
||||
push(context,
|
||||
&(cregex_node_t) { .type = REGEX_NODE_TYPE_ALTERNATION,
|
||||
.left = left,
|
||||
.right = right });
|
||||
}
|
||||
return bottom;
|
||||
}
|
||||
|
||||
#define QUANTIFIER(ch, min, max) \
|
||||
case ch: \
|
||||
if (context->stack == bottom) \
|
||||
goto CHARACTER; \
|
||||
push(context, \
|
||||
&(cregex_node_t) { \
|
||||
.type = REGEX_NODE_TYPE_QUANTIFIER, \
|
||||
.nmin = min, \
|
||||
.nmax = max, \
|
||||
.greedy = (*context->sp == '?') ? (++context->sp, 0) : 1, \
|
||||
.quantified = consume(context) \
|
||||
} \
|
||||
); \
|
||||
break
|
||||
|
||||
/* clang-format off */
|
||||
/* Quantifiers */
|
||||
QUANTIFIER('?', 0, 1);
|
||||
QUANTIFIER('*', 0, -1);
|
||||
QUANTIFIER('+', 1, -1);
|
||||
/* clang-format on */
|
||||
#undef QUANTIFIER
|
||||
|
||||
case '{':
|
||||
if ((context->stack == bottom) || !parse_interval(context)) {
|
||||
goto CHARACTER;
|
||||
}
|
||||
break;
|
||||
|
||||
/* Anchors */
|
||||
case '^':
|
||||
push(context,
|
||||
&(cregex_node_t) { .type = REGEX_NODE_TYPE_ANCHOR_BEGIN });
|
||||
break;
|
||||
case '$':
|
||||
push(context, &(cregex_node_t) { .type = REGEX_NODE_TYPE_ANCHOR_END });
|
||||
break;
|
||||
|
||||
/* Captures */
|
||||
case '(':
|
||||
if (!parse_context(context, depth + 1)) {
|
||||
return NULL;
|
||||
}
|
||||
push(context, &(cregex_node_t) { .type = REGEX_NODE_TYPE_CAPTURE,
|
||||
.captured = consume(context) });
|
||||
break;
|
||||
case ')':
|
||||
if (depth > 0) {
|
||||
return concatenate(context, bottom);
|
||||
}
|
||||
/* unmatched close parenthesis */
|
||||
return NULL;
|
||||
|
||||
/* End of string */
|
||||
case '\0':
|
||||
if (depth == 0) {
|
||||
return concatenate(context, bottom);
|
||||
}
|
||||
/* unmatched open parenthesis */
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline int estimate_nodes(const char *pattern) {
|
||||
return strlen(pattern) * 2;
|
||||
}
|
||||
|
||||
/* Parse a pattern (using a previously allocated buffer of at least
|
||||
* estimate_nodes(pattern) nodes).
|
||||
*/
|
||||
static cregex_node_t* parse_with_nodes(
|
||||
const char *pattern,
|
||||
cregex_node_t *nodes
|
||||
) {
|
||||
regex_parse_context *context
|
||||
= &(regex_parse_context) {
|
||||
.sp = pattern,
|
||||
.stack = nodes,
|
||||
.output = nodes + estimate_nodes(pattern)
|
||||
};
|
||||
return parse_context(context, 0);
|
||||
}
|
||||
|
||||
cregex_node_t* cregex_parse(const char *pattern) {
|
||||
size_t size = sizeof(cregex_node_t) * estimate_nodes(pattern);
|
||||
cregex_node_t *nodes = malloc(size);
|
||||
if (!nodes) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!parse_with_nodes(pattern, nodes)) {
|
||||
free(nodes);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
void cregex_parse_free(cregex_node_t *root) {
|
||||
free(root);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
link_libraries(iowow_s ${CUNIT_LIBRARIES})
|
||||
include_directories(${CUNIT_INCLUDE_DIRS})
|
||||
|
||||
set(TEST_DATA_DIR ${CMAKE_CURRENT_BINARY_DIR})
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${TEST_DATA_DIR})
|
||||
|
||||
foreach(TN IN ITEMS iwre_test1)
|
||||
add_executable(${TN} ${TN}.c)
|
||||
set_target_properties(${TN} PROPERTIES COMPILE_FLAGS "-DIW_STATIC")
|
||||
add_test(
|
||||
NAME ${TN}
|
||||
WORKING_DIRECTORY ${TEST_DATA_DIR}
|
||||
COMMAND ${TEST_TOOL_CMD} $<TARGET_FILE:${TN}>)
|
||||
endforeach()
|
||||
@@ -0,0 +1,66 @@
|
||||
#include "iowow.h"
|
||||
#include <CUnit/Basic.h>
|
||||
#include "iwre.h"
|
||||
|
||||
static int init_suite(void) {
|
||||
return iw_init();
|
||||
}
|
||||
|
||||
static int clean_suite(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void iwre_test1(void) {
|
||||
struct iwre *re = iwre_create("^(one1)(two)?");
|
||||
const char *mpairs[10];
|
||||
int rv = iwre_match(re, "one1two", mpairs, 10);
|
||||
CU_ASSERT_EQUAL(rv, 3);
|
||||
for (int i = 0, j = 0; i < 2 * rv; i += 2, ++j) {
|
||||
intptr_t l = mpairs[i + 1] - mpairs[i];
|
||||
switch(j) {
|
||||
case 0:
|
||||
CU_ASSERT_EQUAL(l, 7);
|
||||
CU_ASSERT_EQUAL(strncmp(mpairs[i], "one1two", l), 0);
|
||||
break;
|
||||
case 1:
|
||||
CU_ASSERT_EQUAL(l, 4);
|
||||
CU_ASSERT_EQUAL(strncmp(mpairs[i], "one1", l), 0);
|
||||
break;
|
||||
case 2:
|
||||
CU_ASSERT_EQUAL(l, 3);
|
||||
CU_ASSERT_EQUAL(strncmp(mpairs[i], "two", l), 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
iwre_destroy(re);
|
||||
}
|
||||
|
||||
int main(int argc, const char *argv[]) {
|
||||
CU_pSuite pSuite = NULL;
|
||||
|
||||
/* Initialize the CUnit test registry */
|
||||
if (CUE_SUCCESS != CU_initialize_registry()) {
|
||||
return CU_get_error();
|
||||
}
|
||||
|
||||
/* Add a suite to the registry */
|
||||
pSuite = CU_add_suite("iwre_test1", init_suite, clean_suite);
|
||||
|
||||
if (NULL == pSuite) {
|
||||
CU_cleanup_registry();
|
||||
return CU_get_error();
|
||||
}
|
||||
|
||||
/* Add the tests to the suite */
|
||||
if ((NULL == CU_add_test(pSuite, "iwre_test1", iwre_test1))) {
|
||||
CU_cleanup_registry();
|
||||
return CU_get_error();
|
||||
}
|
||||
|
||||
/* Run all tests using the CUnit Basic interface */
|
||||
CU_basic_set_mode(CU_BRM_VERBOSE);
|
||||
CU_basic_run_tests();
|
||||
int ret = CU_get_error() || CU_get_number_of_failures();
|
||||
CU_cleanup_registry();
|
||||
return ret;
|
||||
}
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "iwre.h"
|
||||
#include "cregex.h"
|
||||
|
||||
#define REGEX_VM_MAX_MATCHES IWRE_MAX_MATCHES
|
||||
|
||||
/* The VM executes one or more threads, each running a regular expression
|
||||
* program, which is just a list of regular expression instructions. Each
|
||||
* thread maintains two registers while it runs: a program counter (PC) and
|
||||
* a string pointer (SP).
|
||||
*/
|
||||
typedef struct {
|
||||
int visited;
|
||||
const cregex_program_instr_t *pc;
|
||||
const char *matches[REGEX_VM_MAX_MATCHES];
|
||||
} vm_thread;
|
||||
|
||||
/* Run program on string */
|
||||
static int vm_run(
|
||||
const cregex_program_t *program,
|
||||
const char *string,
|
||||
const char **matches,
|
||||
int nmatches);
|
||||
|
||||
/* Run program on string (using a previously allocated buffer of at least
|
||||
* vm_estimate_threads(program) threads)
|
||||
*/
|
||||
static int vm_run_with_threads(
|
||||
const cregex_program_t *program,
|
||||
const char *string,
|
||||
const char **matches,
|
||||
int nmatches,
|
||||
vm_thread *threads);
|
||||
|
||||
typedef struct {
|
||||
int nthreads;
|
||||
vm_thread *threads;
|
||||
} vm_thread_list;
|
||||
|
||||
static void vm_add_thread(
|
||||
vm_thread_list *list,
|
||||
const cregex_program_t *program,
|
||||
const cregex_program_instr_t *pc,
|
||||
const char *string,
|
||||
const char *sp,
|
||||
const char **matches,
|
||||
int nmatches
|
||||
) {
|
||||
if (list->threads[pc - program->instructions].visited == sp - string + 1) {
|
||||
return;
|
||||
}
|
||||
list->threads[pc - program->instructions].visited = sp - string + 1;
|
||||
|
||||
switch (pc->opcode) {
|
||||
case REGEX_PROGRAM_OPCODE_MATCH:
|
||||
/* fall-through */
|
||||
|
||||
/* Characters */
|
||||
case REGEX_PROGRAM_OPCODE_CHARACTER:
|
||||
case REGEX_PROGRAM_OPCODE_ANY_CHARACTER:
|
||||
case REGEX_PROGRAM_OPCODE_CHARACTER_CLASS:
|
||||
case REGEX_PROGRAM_OPCODE_CHARACTER_CLASS_NEGATED:
|
||||
list->threads[list->nthreads].pc = pc;
|
||||
memcpy(list->threads[list->nthreads].matches, matches,
|
||||
sizeof(matches[0]) * ((nmatches <= REGEX_VM_MAX_MATCHES)
|
||||
? nmatches
|
||||
: REGEX_VM_MAX_MATCHES));
|
||||
++list->nthreads;
|
||||
break;
|
||||
|
||||
/* Control-flow */
|
||||
case REGEX_PROGRAM_OPCODE_SPLIT:
|
||||
vm_add_thread(list, program, pc->first, string, sp, matches, nmatches);
|
||||
vm_add_thread(list, program, pc->second, string, sp, matches, nmatches);
|
||||
break;
|
||||
case REGEX_PROGRAM_OPCODE_JUMP:
|
||||
vm_add_thread(list, program, pc->target, string, sp, matches, nmatches);
|
||||
break;
|
||||
|
||||
/* Assertions */
|
||||
case REGEX_PROGRAM_OPCODE_ASSERT_BEGIN:
|
||||
if (sp == string) {
|
||||
vm_add_thread(list, program, pc + 1, string, sp, matches, nmatches);
|
||||
}
|
||||
break;
|
||||
case REGEX_PROGRAM_OPCODE_ASSERT_END:
|
||||
if (!*sp) {
|
||||
vm_add_thread(list, program, pc + 1, string, sp, matches, nmatches);
|
||||
}
|
||||
break;
|
||||
|
||||
/* Saving */
|
||||
case REGEX_PROGRAM_OPCODE_SAVE:
|
||||
if (pc->save < nmatches && pc->save < REGEX_VM_MAX_MATCHES) {
|
||||
const char *saved = matches[pc->save];
|
||||
matches[pc->save] = sp;
|
||||
vm_add_thread(list, program, pc + 1, string, sp, matches, nmatches);
|
||||
matches[pc->save] = saved;
|
||||
} else {
|
||||
vm_add_thread(list, program, pc + 1, string, sp, matches, nmatches);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Upper bound of number of threads required to run program */
|
||||
static int vm_estimate_threads(const cregex_program_t *program) {
|
||||
return program->ninstructions * 2;
|
||||
}
|
||||
|
||||
static int vm_run(
|
||||
const cregex_program_t *program,
|
||||
const char *string,
|
||||
const char **matches,
|
||||
int nmatches
|
||||
) {
|
||||
size_t size = sizeof(vm_thread) * vm_estimate_threads(program);
|
||||
vm_thread *threads;
|
||||
int matched;
|
||||
|
||||
if (!(threads = malloc(size))) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
matched = vm_run_with_threads(program, string, matches, nmatches, threads);
|
||||
free(threads);
|
||||
return matched;
|
||||
}
|
||||
|
||||
static int vm_run_with_threads(
|
||||
const cregex_program_t *program,
|
||||
const char *string,
|
||||
const char **matches,
|
||||
int nmatches,
|
||||
vm_thread *threads
|
||||
) {
|
||||
vm_thread_list *current
|
||||
= &(vm_thread_list) {
|
||||
.nthreads = 0, .threads = threads
|
||||
};
|
||||
vm_thread_list *next = &(vm_thread_list) {
|
||||
.nthreads = 0, .threads = threads + program->ninstructions
|
||||
};
|
||||
int matched = 0;
|
||||
|
||||
memset(threads, 0, sizeof(vm_thread) * program->ninstructions * 2);
|
||||
|
||||
vm_add_thread(current, program, program->instructions, string, string,
|
||||
matches, nmatches);
|
||||
|
||||
for (const char *sp = string; ; ++sp) {
|
||||
for (int i = 0; i < current->nthreads; ++i) {
|
||||
vm_thread *thread = current->threads + i;
|
||||
switch (thread->pc->opcode) {
|
||||
case REGEX_PROGRAM_OPCODE_MATCH:
|
||||
matched = 1;
|
||||
current->nthreads = 0;
|
||||
memcpy(matches, thread->matches,
|
||||
sizeof(matches[0]) * ((nmatches <= REGEX_VM_MAX_MATCHES)
|
||||
? nmatches
|
||||
: REGEX_VM_MAX_MATCHES));
|
||||
continue;
|
||||
|
||||
/* Characters */
|
||||
case REGEX_PROGRAM_OPCODE_CHARACTER:
|
||||
if (*sp == thread->pc->ch) {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
case REGEX_PROGRAM_OPCODE_ANY_CHARACTER:
|
||||
if (*sp) {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
case REGEX_PROGRAM_OPCODE_CHARACTER_CLASS:
|
||||
if (cregex_char_class_contains(thread->pc->klass, *sp)) {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
case REGEX_PROGRAM_OPCODE_CHARACTER_CLASS_NEGATED:
|
||||
if (!cregex_char_class_contains(thread->pc->klass, *sp)) {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
|
||||
/* Control-flow */
|
||||
case REGEX_PROGRAM_OPCODE_SPLIT:
|
||||
case REGEX_PROGRAM_OPCODE_JUMP:
|
||||
/* fall-through */
|
||||
|
||||
/* Assertions */
|
||||
case REGEX_PROGRAM_OPCODE_ASSERT_BEGIN:
|
||||
case REGEX_PROGRAM_OPCODE_ASSERT_END:
|
||||
/* fall-through */
|
||||
|
||||
/* Saving */
|
||||
case REGEX_PROGRAM_OPCODE_SAVE:
|
||||
/* handled in vm_add_thread() */
|
||||
abort();
|
||||
}
|
||||
|
||||
vm_add_thread(next, program, thread->pc + 1, string, sp + 1,
|
||||
thread->matches, nmatches);
|
||||
}
|
||||
|
||||
/* swap current and next thread list */
|
||||
vm_thread_list *swap = current;
|
||||
current = next;
|
||||
next = swap;
|
||||
next->nthreads = 0;
|
||||
|
||||
/* done if no more threads are running or end of string reached */
|
||||
if (current->nthreads == 0 || !*sp) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return matched;
|
||||
}
|
||||
|
||||
int cregex_program_run(
|
||||
const cregex_program_t *program,
|
||||
const char *string,
|
||||
const char **matches,
|
||||
int nmatches
|
||||
) {
|
||||
return vm_run(program, string, matches, nmatches);
|
||||
}
|
||||
+28
-8
@@ -13,8 +13,8 @@ off_t iwarr_sorted_insert(
|
||||
size_t elsize,
|
||||
void* restrict eptr,
|
||||
int (*cmp)(const void*, const void*),
|
||||
bool skipeq) {
|
||||
|
||||
bool skipeq
|
||||
) {
|
||||
#define EL(idx_) (elsptr + (idx_) * elsize)
|
||||
|
||||
off_t idx = 0,
|
||||
@@ -58,8 +58,8 @@ off_t iwarr_sorted_remove(
|
||||
size_t nels,
|
||||
size_t elsize,
|
||||
void* restrict eptr,
|
||||
int (*cmp)(const void*, const void*)) {
|
||||
|
||||
int (*cmp)(const void*, const void*)
|
||||
) {
|
||||
#define EL(idx_) (elsptr + (idx_) * elsize)
|
||||
|
||||
off_t idx = 0,
|
||||
@@ -99,8 +99,8 @@ off_t iwarr_sorted_find(
|
||||
size_t nels,
|
||||
size_t elsize,
|
||||
void* restrict eptr,
|
||||
int (*cmp)(const void*, const void*)) {
|
||||
|
||||
int (*cmp)(const void*, const void*)
|
||||
) {
|
||||
#define EL(idx_) (elsptr + (idx_) * elsize)
|
||||
|
||||
off_t idx = 0,
|
||||
@@ -139,8 +139,8 @@ off_t iwarr_sorted_find2(
|
||||
void* restrict eptr,
|
||||
void *op,
|
||||
bool *found,
|
||||
iwrc (*cmp)(const void*, const void*, void*, int *res)) {
|
||||
|
||||
iwrc (*cmp)(const void*, const void*, void*, int *res)
|
||||
) {
|
||||
#define EL(idx_) (elsptr + (idx_) * elsize)
|
||||
|
||||
off_t idx = 0,
|
||||
@@ -395,6 +395,26 @@ iwrc iwulist_remove(IWULIST *list, size_t index) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool iwulist_remove_first_by(IWULIST *list, void *data_ptr) {
|
||||
for (size_t i = list->start; i < list->start + list->num; ++i) {
|
||||
void *ptr = list->array + i * list->usize;
|
||||
if (memcmp(data_ptr, ptr, list->usize) == 0) {
|
||||
return iwulist_remove(list, i - list->start) == 0;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ssize_t iwulist_find_first(IWULIST *list, void *data_ptr) {
|
||||
for (size_t i = list->start; i < list->start + list->num; ++i) {
|
||||
void *ptr = list->array + i * list->usize;
|
||||
if (memcmp(data_ptr, ptr, list->usize) == 0) {
|
||||
return i - list->start;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
iwrc iwulist_unshift(IWULIST *list, const void *data) {
|
||||
if (!list->start) {
|
||||
if (list->num >= list->anum) {
|
||||
|
||||
@@ -161,6 +161,24 @@ IW_EXPORT iwrc iwulist_set(IWULIST *list, size_t index, const void *data);
|
||||
*/
|
||||
IW_EXPORT iwrc iwulist_remove(IWULIST *list, size_t index);
|
||||
|
||||
/**
|
||||
* @brief Removes first element matches the given `data_ptr` content.
|
||||
* @note `data_ptr` buffer must at least of list unit size.
|
||||
*
|
||||
* @param data_ptr Pointer to data buffer list items will be matched against.
|
||||
* @return True if matched element was found.
|
||||
*/
|
||||
IW_EXPORT bool iwulist_remove_first_by(IWULIST *list, void *data_ptr);
|
||||
|
||||
/**
|
||||
* @brief Finds first element matched the given `data_ptr` content.
|
||||
* @note `data_ptr` buffer must at least of list unit size.
|
||||
*
|
||||
* @param data_ptr Pointer to data buffer list items will be matched against.
|
||||
* @return Index of first matched element or `-1` if item not found.
|
||||
*/
|
||||
IW_EXPORT ssize_t iwulist_find_first(IWULIST *list, void *data_ptr);
|
||||
|
||||
/**
|
||||
* @brief Adds new element to end of list.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,789 @@
|
||||
#include "iwavl.h"
|
||||
|
||||
/*
|
||||
* avl_tree.c - intrusive, nonrecursive AVL tree data structure (self-balancing
|
||||
* binary search tree), implementation file
|
||||
*
|
||||
* Written in 2014-2016 by Eric Biggers <ebiggers3@gmail.com>
|
||||
*
|
||||
* To the extent possible under law, the author(s) have dedicated all copyright
|
||||
* and related and neighboring rights to this software to the public domain
|
||||
* worldwide via the Creative Commons Zero 1.0 Universal Public Domain
|
||||
* Dedication (the "CC0").
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the CC0 for more details.
|
||||
*
|
||||
* You should have received a copy of the CC0 along with this software; if not
|
||||
* see <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||
*/
|
||||
|
||||
|
||||
/* Returns the left child (sign < 0) or the right child (sign > 0) of the
|
||||
* specified AVL tree node.
|
||||
* Note: for all calls of this, 'sign' is constant at compilation time,
|
||||
* so the compiler can remove the conditional. */
|
||||
IW_INLINE struct iwavl_node* iwavl_get_child(const struct iwavl_node *parent, int sign) {
|
||||
if (sign < 0) {
|
||||
return parent->left;
|
||||
} else {
|
||||
return parent->right;
|
||||
}
|
||||
}
|
||||
|
||||
IW_INLINE struct iwavl_node* iwavl_first_or_last_in_order(const struct iwavl_node *root, int sign) {
|
||||
const struct iwavl_node *first = root;
|
||||
|
||||
if (first) {
|
||||
while (iwavl_get_child(first, +sign)) {
|
||||
first = iwavl_get_child(first, +sign);
|
||||
}
|
||||
}
|
||||
return (struct iwavl_node*) first;
|
||||
}
|
||||
|
||||
/* Starts an in-order traversal of the tree: returns the least-valued node, or
|
||||
* 0 if the tree is empty. */
|
||||
struct iwavl_node* iwavl_first_in_order(const struct iwavl_node *root) {
|
||||
return iwavl_first_or_last_in_order(root, -1);
|
||||
}
|
||||
|
||||
/* Starts a *reverse* in-order traversal of the tree: returns the
|
||||
* greatest-valued node, or 0 if the tree is empty. */
|
||||
struct iwavl_node* iwavl_last_in_order(const struct iwavl_node *root) {
|
||||
return iwavl_first_or_last_in_order(root, 1);
|
||||
}
|
||||
|
||||
IW_INLINE struct iwavl_node* iwavl_next_or_prev_in_order(const struct iwavl_node *node, int sign) {
|
||||
const struct iwavl_node *next;
|
||||
|
||||
if (iwavl_get_child(node, +sign)) {
|
||||
for (next = iwavl_get_child(node, +sign);
|
||||
iwavl_get_child(next, -sign);
|
||||
next = iwavl_get_child(next, -sign)) {
|
||||
;
|
||||
}
|
||||
} else {
|
||||
for (next = iwavl_get_parent(node);
|
||||
next && node == iwavl_get_child(next, +sign);
|
||||
node = next, next = iwavl_get_parent(next)) {
|
||||
;
|
||||
}
|
||||
}
|
||||
return (struct iwavl_node*) next;
|
||||
}
|
||||
|
||||
/* Continues an in-order traversal of the tree: returns the next-greatest-valued
|
||||
* node, or 0 if there is none. */
|
||||
struct iwavl_node* iwavl_next_in_order(const struct iwavl_node *node) {
|
||||
return iwavl_next_or_prev_in_order(node, 1);
|
||||
}
|
||||
|
||||
/* Continues a *reverse* in-order traversal of the tree: returns the
|
||||
* previous-greatest-valued node, or 0 if there is none. */
|
||||
struct iwavl_node* iwavl_prev_in_order(const struct iwavl_node *node) {
|
||||
return iwavl_next_or_prev_in_order(node, -1);
|
||||
}
|
||||
|
||||
/* Starts a postorder traversal of the tree. */
|
||||
struct iwavl_node* iwavl_first_in_postorder(const struct iwavl_node *root) {
|
||||
const struct iwavl_node *first = root;
|
||||
|
||||
if (first) {
|
||||
while (first->left || first->right) {
|
||||
first = first->left ? first->left : first->right;
|
||||
}
|
||||
}
|
||||
|
||||
return (struct iwavl_node*) first;
|
||||
}
|
||||
|
||||
/* Continues a postorder traversal of the tree. @prev will not be deferenced as
|
||||
* it's allowed that its memory has been freed; @prev_parent must be its saved
|
||||
* parent node. Returns 0 if there are no more nodes (i.e. @prev was the
|
||||
* root of the tree). */
|
||||
struct iwavl_node* iwavl_next_in_postorder(
|
||||
const struct iwavl_node *prev,
|
||||
const struct iwavl_node *prev_parent
|
||||
) {
|
||||
const struct iwavl_node *next = prev_parent;
|
||||
|
||||
if (next && prev == next->left && next->right) {
|
||||
for (next = next->right;
|
||||
next->left || next->right;
|
||||
next = next->left ? next->left : next->right) {
|
||||
;
|
||||
}
|
||||
}
|
||||
return (struct iwavl_node*) next;
|
||||
}
|
||||
|
||||
/* Sets the left child (sign < 0) or the right child (sign > 0) of the
|
||||
* specified AVL tree node.
|
||||
* Note: for all calls of this, 'sign' is constant at compilation time,
|
||||
* so the compiler can remove the conditional. */
|
||||
IW_INLINE void avl_set_child(
|
||||
struct iwavl_node *parent, int sign,
|
||||
struct iwavl_node *child
|
||||
) {
|
||||
if (sign < 0) {
|
||||
parent->left = child;
|
||||
} else {
|
||||
parent->right = child;
|
||||
}
|
||||
}
|
||||
|
||||
/* Sets the parent and balance factor of the specified AVL tree node. */
|
||||
IW_INLINE void avl_set_parent_balance(
|
||||
struct iwavl_node *node, struct iwavl_node *parent,
|
||||
int balance_factor
|
||||
) {
|
||||
node->parent_balance = (uintptr_t) parent | (balance_factor + 1);
|
||||
}
|
||||
|
||||
/* Sets the parent of the specified AVL tree node. */
|
||||
IW_INLINE void avl_set_parent(struct iwavl_node *node, struct iwavl_node *parent) {
|
||||
node->parent_balance = (uintptr_t) parent | (node->parent_balance & 3);
|
||||
}
|
||||
|
||||
/* Returns the balance factor of the specified AVL tree node --- that is, the
|
||||
* height of its right subtree minus the height of its left subtree. */
|
||||
IW_INLINE int avl_get_balance_factor(const struct iwavl_node *node) {
|
||||
return (int) (node->parent_balance & 3) - 1;
|
||||
}
|
||||
|
||||
/* Adds @amount to the balance factor of the specified AVL tree node.
|
||||
* The caller must ensure this still results in a valid balance factor
|
||||
* (-1, 0, or 1). */
|
||||
IW_INLINE void avl_adjust_balance_factor(struct iwavl_node *node, int amount) {
|
||||
node->parent_balance += amount;
|
||||
}
|
||||
|
||||
IW_INLINE void avl_replace_child(
|
||||
struct iwavl_node **root_ptr,
|
||||
struct iwavl_node *parent,
|
||||
struct iwavl_node *old_child,
|
||||
struct iwavl_node *new_child
|
||||
) {
|
||||
if (parent) {
|
||||
if (old_child == parent->left) {
|
||||
parent->left = new_child;
|
||||
} else {
|
||||
parent->right = new_child;
|
||||
}
|
||||
} else {
|
||||
*root_ptr = new_child;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Template for performing a single rotation ---
|
||||
*
|
||||
* sign > 0: Rotate clockwise (right) rooted at A:
|
||||
*
|
||||
* P? P?
|
||||
* | |
|
||||
* A B
|
||||
* / \ / \
|
||||
* B C? => D? A
|
||||
* / \ / \
|
||||
* D? E? E? C?
|
||||
*
|
||||
* (nodes marked with ? may not exist)
|
||||
*
|
||||
* sign < 0: Rotate counterclockwise (left) rooted at A:
|
||||
*
|
||||
* P? P?
|
||||
* | |
|
||||
* A B
|
||||
* / \ / \
|
||||
* C? B => A D?
|
||||
* / \ / \
|
||||
* E? D? C? E?
|
||||
*
|
||||
* This updates pointers but not balance factors!
|
||||
*/
|
||||
IW_INLINE void avl_rotate(
|
||||
struct iwavl_node** const root_ptr,
|
||||
struct iwavl_node* const A, const int sign
|
||||
) {
|
||||
struct iwavl_node* const B = iwavl_get_child(A, -sign);
|
||||
struct iwavl_node* const E = iwavl_get_child(B, +sign);
|
||||
struct iwavl_node* const P = iwavl_get_parent(A);
|
||||
|
||||
avl_set_child(A, -sign, E);
|
||||
avl_set_parent(A, B);
|
||||
|
||||
avl_set_child(B, +sign, A);
|
||||
avl_set_parent(B, P);
|
||||
|
||||
if (E) {
|
||||
avl_set_parent(E, A);
|
||||
}
|
||||
|
||||
avl_replace_child(root_ptr, P, A, B);
|
||||
}
|
||||
|
||||
/*
|
||||
* Template for performing a double rotation ---
|
||||
*
|
||||
* sign > 0: Rotate counterclockwise (left) rooted at B, then
|
||||
* clockwise (right) rooted at A:
|
||||
*
|
||||
* P? P? P?
|
||||
* | | |
|
||||
* A A E
|
||||
* / \ / \ / \
|
||||
* B C? => E C? => B A
|
||||
* / \ / \ / \ / \
|
||||
* D? E B G? D? F?G? C?
|
||||
* / \ / \
|
||||
* F? G? D? F?
|
||||
*
|
||||
* (nodes marked with ? may not exist)
|
||||
*
|
||||
* sign < 0: Rotate clockwise (right) rooted at B, then
|
||||
* counterclockwise (left) rooted at A:
|
||||
*
|
||||
* P? P? P?
|
||||
* | | |
|
||||
* A A E
|
||||
* / \ / \ / \
|
||||
* C? B => C? E => A B
|
||||
* / \ / \ / \ / \
|
||||
* E D? G? B C? G?F? D?
|
||||
* / \ / \
|
||||
* G? F? F? D?
|
||||
*
|
||||
* Returns a pointer to E and updates balance factors. Except for those
|
||||
* two things, this function is equivalent to:
|
||||
* avl_rotate(root_ptr, B, -sign);
|
||||
* avl_rotate(root_ptr, A, +sign);
|
||||
*
|
||||
* See comment in avl_handle_subtree_growth() for explanation of balance
|
||||
* factor updates.
|
||||
*/
|
||||
IW_INLINE struct iwavl_node* avl_do_double_rotate(
|
||||
struct iwavl_node** const root_ptr,
|
||||
struct iwavl_node* const B,
|
||||
struct iwavl_node* const A, const int sign
|
||||
) {
|
||||
struct iwavl_node* const E = iwavl_get_child(B, +sign);
|
||||
struct iwavl_node* const F = iwavl_get_child(E, -sign);
|
||||
struct iwavl_node* const G = iwavl_get_child(E, +sign);
|
||||
struct iwavl_node* const P = iwavl_get_parent(A);
|
||||
const int e = avl_get_balance_factor(E);
|
||||
|
||||
avl_set_child(A, -sign, G);
|
||||
avl_set_parent_balance(A, E, ((sign * e >= 0) ? 0 : -e));
|
||||
|
||||
avl_set_child(B, +sign, F);
|
||||
avl_set_parent_balance(B, E, ((sign * e <= 0) ? 0 : -e));
|
||||
|
||||
avl_set_child(E, +sign, A);
|
||||
avl_set_child(E, -sign, B);
|
||||
avl_set_parent_balance(E, P, 0);
|
||||
|
||||
if (G) {
|
||||
avl_set_parent(G, A);
|
||||
}
|
||||
|
||||
if (F) {
|
||||
avl_set_parent(F, B);
|
||||
}
|
||||
|
||||
avl_replace_child(root_ptr, P, A, E);
|
||||
|
||||
return E;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function handles the growth of a subtree due to an insertion.
|
||||
*
|
||||
* @root_ptr
|
||||
* Location of the tree's root pointer.
|
||||
*
|
||||
* @node
|
||||
* A subtree that has increased in height by 1 due to an insertion.
|
||||
*
|
||||
* @parent
|
||||
* Parent of @node; must not be 0.
|
||||
*
|
||||
* @sign
|
||||
* -1 if @node is the left child of @parent;
|
||||
* +1 if @node is the right child of @parent.
|
||||
*
|
||||
* This function will adjust @parent's balance factor, then do a (single
|
||||
* or double) rotation if necessary. The return value will be %true if
|
||||
* the full AVL tree is now adequately balanced, or %false if the subtree
|
||||
* rooted at @parent is now adequately balanced but has increased in
|
||||
* height by 1, so the caller should continue up the tree.
|
||||
*
|
||||
* Note that if %false is returned, no rotation will have been done.
|
||||
* Indeed, a single node insertion cannot require that more than one
|
||||
* (single or double) rotation be done.
|
||||
*/
|
||||
IW_INLINE bool avl_handle_subtree_growth(
|
||||
struct iwavl_node** const root_ptr,
|
||||
struct iwavl_node* const node,
|
||||
struct iwavl_node* const parent,
|
||||
const int sign
|
||||
) {
|
||||
int old_balance_factor, new_balance_factor;
|
||||
|
||||
old_balance_factor = avl_get_balance_factor(parent);
|
||||
|
||||
if (old_balance_factor == 0) {
|
||||
avl_adjust_balance_factor(parent, sign);
|
||||
/* @parent is still sufficiently balanced (-1 or +1
|
||||
* balance factor), but must have increased in height.
|
||||
* Continue up the tree. */
|
||||
return false;
|
||||
}
|
||||
|
||||
new_balance_factor = old_balance_factor + sign;
|
||||
|
||||
if (new_balance_factor == 0) {
|
||||
avl_adjust_balance_factor(parent, sign);
|
||||
/* @parent is now perfectly balanced (0 balance factor).
|
||||
* It cannot have increased in height, so there is
|
||||
* nothing more to do. */
|
||||
return true;
|
||||
}
|
||||
|
||||
/* @parent is too left-heavy (new_balance_factor == -2) or
|
||||
* too right-heavy (new_balance_factor == +2). */
|
||||
|
||||
/* Test whether @node is left-heavy (-1 balance factor) or
|
||||
* right-heavy (+1 balance factor).
|
||||
* Note that it cannot be perfectly balanced (0 balance factor)
|
||||
* because here we are under the invariant that @node has
|
||||
* increased in height due to the insertion. */
|
||||
if (sign * avl_get_balance_factor(node) > 0) {
|
||||
/* @node (B below) is heavy in the same direction @parent
|
||||
* (A below) is heavy.
|
||||
*
|
||||
* @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
* The comment, diagram, and equations below assume sign < 0.
|
||||
* The other case is symmetric!
|
||||
* @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
*
|
||||
* Do a clockwise rotation rooted at @parent (A below):
|
||||
*
|
||||
* A B
|
||||
* / \ / \
|
||||
* B C? => D A
|
||||
* / \ / \ / \
|
||||
* D E? F? G?E? C?
|
||||
* / \
|
||||
* F? G?
|
||||
*
|
||||
* Before the rotation:
|
||||
* balance(A) = -2
|
||||
* balance(B) = -1
|
||||
* Let x = height(C). Then:
|
||||
* height(B) = x + 2
|
||||
* height(D) = x + 1
|
||||
* height(E) = x
|
||||
* max(height(F), height(G)) = x.
|
||||
*
|
||||
* After the rotation:
|
||||
* height(D) = max(height(F), height(G)) + 1
|
||||
* = x + 1
|
||||
* height(A) = max(height(E), height(C)) + 1
|
||||
* = max(x, x) + 1 = x + 1
|
||||
* balance(B) = 0
|
||||
* balance(A) = 0
|
||||
*/
|
||||
avl_rotate(root_ptr, parent, -sign);
|
||||
|
||||
/* Equivalent to setting @parent's balance factor to 0. */
|
||||
avl_adjust_balance_factor(parent, -sign); /* A */
|
||||
|
||||
/* Equivalent to setting @node's balance factor to 0. */
|
||||
avl_adjust_balance_factor(node, -sign); /* B */
|
||||
} else {
|
||||
/* @node (B below) is heavy in the direction opposite
|
||||
* from the direction @parent (A below) is heavy.
|
||||
*
|
||||
* @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
* The comment, diagram, and equations below assume sign < 0.
|
||||
* The other case is symmetric!
|
||||
* @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
*
|
||||
* Do a counterblockwise rotation rooted at @node (B below),
|
||||
* then a clockwise rotation rooted at @parent (A below):
|
||||
*
|
||||
* A A E
|
||||
* / \ / \ / \
|
||||
* B C? => E C? => B A
|
||||
* / \ / \ / \ / \
|
||||
* D? E B G? D? F?G? C?
|
||||
* / \ / \
|
||||
* F? G? D? F?
|
||||
*
|
||||
* Before the rotation:
|
||||
* balance(A) = -2
|
||||
* balance(B) = +1
|
||||
* Let x = height(C). Then:
|
||||
* height(B) = x + 2
|
||||
* height(E) = x + 1
|
||||
* height(D) = x
|
||||
* max(height(F), height(G)) = x
|
||||
*
|
||||
* After both rotations:
|
||||
* height(A) = max(height(G), height(C)) + 1
|
||||
* = x + 1
|
||||
* balance(A) = balance(E{orig}) >= 0 ? 0 : -balance(E{orig})
|
||||
* height(B) = max(height(D), height(F)) + 1
|
||||
* = x + 1
|
||||
* balance(B) = balance(E{orig} <= 0) ? 0 : -balance(E{orig})
|
||||
*
|
||||
* height(E) = x + 2
|
||||
* balance(E) = 0
|
||||
*/
|
||||
avl_do_double_rotate(root_ptr, node, parent, -sign);
|
||||
}
|
||||
|
||||
/* Height after rotation is unchanged; nothing more to do. */
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Rebalance the tree after insertion of the specified node. */
|
||||
void iwavl_rebalance_after_insert(
|
||||
struct iwavl_node **root_ptr,
|
||||
struct iwavl_node *inserted
|
||||
) {
|
||||
struct iwavl_node *node, *parent;
|
||||
bool done;
|
||||
|
||||
inserted->left = 0;
|
||||
inserted->right = 0;
|
||||
|
||||
node = inserted;
|
||||
|
||||
/* Adjust balance factor of new node's parent.
|
||||
* No rotation will need to be done at this level. */
|
||||
|
||||
parent = iwavl_get_parent(node);
|
||||
if (!parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node == parent->left) {
|
||||
avl_adjust_balance_factor(parent, -1);
|
||||
} else {
|
||||
avl_adjust_balance_factor(parent, +1);
|
||||
}
|
||||
|
||||
if (avl_get_balance_factor(parent) == 0) {
|
||||
/* @parent did not change in height. Nothing more to do. */
|
||||
return;
|
||||
}
|
||||
|
||||
/* The subtree rooted at @parent increased in height by 1. */
|
||||
|
||||
do {
|
||||
/* Adjust balance factor of next ancestor. */
|
||||
|
||||
node = parent;
|
||||
parent = iwavl_get_parent(node);
|
||||
if (!parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* The subtree rooted at @node has increased in height by 1. */
|
||||
if (node == parent->left) {
|
||||
done = avl_handle_subtree_growth(root_ptr, node,
|
||||
parent, -1);
|
||||
} else {
|
||||
done = avl_handle_subtree_growth(root_ptr, node,
|
||||
parent, +1);
|
||||
}
|
||||
} while (!done);
|
||||
}
|
||||
|
||||
/*
|
||||
* This function handles the shrinkage of a subtree due to a deletion.
|
||||
*
|
||||
* @root_ptr
|
||||
* Location of the tree's root pointer.
|
||||
*
|
||||
* @parent
|
||||
* A node in the tree, exactly one of whose subtrees has decreased
|
||||
* in height by 1 due to a deletion. (This includes the case where
|
||||
* one of the child pointers has become 0, since we can consider
|
||||
* the "0" subtree to have a height of 0.)
|
||||
*
|
||||
* @sign
|
||||
* +1 if the left subtree of @parent has decreased in height by 1;
|
||||
* -1 if the right subtree of @parent has decreased in height by 1.
|
||||
*
|
||||
* @left_deleted_ret
|
||||
* If the return value is not 0, this will be set to %true if the
|
||||
* left subtree of the returned node has decreased in height by 1,
|
||||
* or %false if the right subtree of the returned node has decreased
|
||||
* in height by 1.
|
||||
*
|
||||
* This function will adjust @parent's balance factor, then do a (single
|
||||
* or double) rotation if necessary. The return value will be 0 if
|
||||
* the full AVL tree is now adequately balanced, or a pointer to the
|
||||
* parent of @parent if @parent is now adequately balanced but has
|
||||
* decreased in height by 1. Also in the latter case, *left_deleted_ret
|
||||
* will be set.
|
||||
*/
|
||||
IW_INLINE struct iwavl_node* avl_handle_subtree_shrink(
|
||||
struct iwavl_node** const root_ptr,
|
||||
struct iwavl_node *parent,
|
||||
const int sign,
|
||||
bool* const left_deleted_ret
|
||||
) {
|
||||
struct iwavl_node *node;
|
||||
int old_balance_factor, new_balance_factor;
|
||||
|
||||
old_balance_factor = avl_get_balance_factor(parent);
|
||||
|
||||
if (old_balance_factor == 0) {
|
||||
/* Prior to the deletion, the subtree rooted at
|
||||
* @parent was perfectly balanced. It's now
|
||||
* unbalanced by 1, but that's okay and its height
|
||||
* hasn't changed. Nothing more to do. */
|
||||
avl_adjust_balance_factor(parent, sign);
|
||||
return 0;
|
||||
}
|
||||
|
||||
new_balance_factor = old_balance_factor + sign;
|
||||
|
||||
if (new_balance_factor == 0) {
|
||||
/* The subtree rooted at @parent is now perfectly
|
||||
* balanced, whereas before the deletion it was
|
||||
* unbalanced by 1. Its height must have decreased
|
||||
* by 1. No rotation is needed at this location,
|
||||
* but continue up the tree. */
|
||||
avl_adjust_balance_factor(parent, sign);
|
||||
node = parent;
|
||||
} else {
|
||||
/* @parent is too left-heavy (new_balance_factor == -2) or
|
||||
* too right-heavy (new_balance_factor == +2). */
|
||||
|
||||
node = iwavl_get_child(parent, sign);
|
||||
|
||||
/* The rotations below are similar to those done during
|
||||
* insertion (see avl_handle_subtree_growth()), so full
|
||||
* comments are not provided. The only new case is the
|
||||
* one where @node has a balance factor of 0, and that is
|
||||
* commented. */
|
||||
|
||||
if (sign * avl_get_balance_factor(node) >= 0) {
|
||||
avl_rotate(root_ptr, parent, -sign);
|
||||
|
||||
if (avl_get_balance_factor(node) == 0) {
|
||||
/*
|
||||
* @node (B below) is perfectly balanced.
|
||||
*
|
||||
* @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
* The comment, diagram, and equations
|
||||
* below assume sign < 0. The other case
|
||||
* is symmetric!
|
||||
* @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||
*
|
||||
* Do a clockwise rotation rooted at
|
||||
* @parent (A below):
|
||||
*
|
||||
* A B
|
||||
* / \ / \
|
||||
* B C? => D A
|
||||
* / \ / \ / \
|
||||
* D E F? G?E C?
|
||||
* / \
|
||||
* F? G?
|
||||
*
|
||||
* Before the rotation:
|
||||
* balance(A) = -2
|
||||
* balance(B) = 0
|
||||
* Let x = height(C). Then:
|
||||
* height(B) = x + 2
|
||||
* height(D) = x + 1
|
||||
* height(E) = x + 1
|
||||
* max(height(F), height(G)) = x.
|
||||
*
|
||||
* After the rotation:
|
||||
* height(D) = max(height(F), height(G)) + 1
|
||||
* = x + 1
|
||||
* height(A) = max(height(E), height(C)) + 1
|
||||
* = max(x + 1, x) + 1 = x + 2
|
||||
* balance(A) = -1
|
||||
* balance(B) = +1
|
||||
*/
|
||||
|
||||
/* A: -2 => -1 (sign < 0)
|
||||
* or +2 => +1 (sign > 0)
|
||||
* No change needed --- that's the same as
|
||||
* old_balance_factor. */
|
||||
|
||||
/* B: 0 => +1 (sign < 0)
|
||||
* or 0 => -1 (sign > 0) */
|
||||
avl_adjust_balance_factor(node, -sign);
|
||||
|
||||
/* Height is unchanged; nothing more to do. */
|
||||
return 0;
|
||||
} else {
|
||||
avl_adjust_balance_factor(parent, -sign);
|
||||
avl_adjust_balance_factor(node, -sign);
|
||||
}
|
||||
} else {
|
||||
node = avl_do_double_rotate(root_ptr, node,
|
||||
parent, -sign);
|
||||
}
|
||||
}
|
||||
parent = iwavl_get_parent(node);
|
||||
if (parent) {
|
||||
*left_deleted_ret = (node == parent->left);
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
|
||||
/* Swaps node X, which must have 2 children, with its in-order successor, then
|
||||
* unlinks node X. Returns the parent of X just before unlinking, without its
|
||||
* balance factor having been updated to account for the unlink. */
|
||||
IW_INLINE struct iwavl_node* avl_tree_swap_with_successor(
|
||||
struct iwavl_node **root_ptr,
|
||||
struct iwavl_node *X,
|
||||
bool *left_deleted_ret
|
||||
) {
|
||||
struct iwavl_node *Y, *ret;
|
||||
|
||||
Y = X->right;
|
||||
if (!Y->left) {
|
||||
/*
|
||||
* P? P? P?
|
||||
* | | |
|
||||
* X Y Y
|
||||
* / \ / \ / \
|
||||
* A Y => A X => A B?
|
||||
* / \ / \
|
||||
* (0) B? (0) B?
|
||||
*
|
||||
* [ X unlinked, Y returned ]
|
||||
*/
|
||||
ret = Y;
|
||||
*left_deleted_ret = false;
|
||||
} else {
|
||||
struct iwavl_node *Q;
|
||||
|
||||
do {
|
||||
Q = Y;
|
||||
Y = Y->left;
|
||||
} while (Y->left);
|
||||
|
||||
/*
|
||||
* P? P? P?
|
||||
* | | |
|
||||
* X Y Y
|
||||
* / \ / \ / \
|
||||
* A ... => A ... => A ...
|
||||
* | | |
|
||||
* Q Q Q
|
||||
* / / /
|
||||
* Y X B?
|
||||
* / \ / \
|
||||
* (0) B? (0) B?
|
||||
*
|
||||
*
|
||||
* [ X unlinked, Q returned ]
|
||||
*/
|
||||
|
||||
Q->left = Y->right;
|
||||
if (Q->left) {
|
||||
avl_set_parent(Q->left, Q);
|
||||
}
|
||||
Y->right = X->right;
|
||||
avl_set_parent(X->right, Y);
|
||||
ret = Q;
|
||||
*left_deleted_ret = true;
|
||||
}
|
||||
|
||||
Y->left = X->left;
|
||||
avl_set_parent(X->left, Y);
|
||||
|
||||
Y->parent_balance = X->parent_balance;
|
||||
avl_replace_child(root_ptr, iwavl_get_parent(X), X, Y);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes an item from the specified AVL tree.
|
||||
*
|
||||
* @root_ptr
|
||||
* Location of the AVL tree's root pointer. Indirection is needed
|
||||
* because the root node may change if the tree needed to be rebalanced
|
||||
* because of the deletion or if @node was the root node.
|
||||
*
|
||||
* @node
|
||||
* Pointer to the `struct iwavl_node' embedded in the item to
|
||||
* remove from the tree.
|
||||
*
|
||||
* Note: This function *only* removes the node and rebalances the tree.
|
||||
* It does not free any memory, nor does it do the equivalent of
|
||||
* iwavl_node_set_unlinked().
|
||||
*/
|
||||
void iwavl_remove(struct iwavl_node **root_ptr, struct iwavl_node *node) {
|
||||
struct iwavl_node *parent;
|
||||
bool left_deleted = false;
|
||||
|
||||
if (node->left && node->right) {
|
||||
/* @node is fully internal, with two children. Swap it
|
||||
* with its in-order successor (which must exist in the
|
||||
* right subtree of @node and can have, at most, a right
|
||||
* child), then unlink @node. */
|
||||
parent = avl_tree_swap_with_successor(root_ptr, node,
|
||||
&left_deleted);
|
||||
/* @parent is now the parent of what was @node's in-order
|
||||
* successor. It cannot be 0, since @node itself was
|
||||
* an ancestor of its in-order successor.
|
||||
* @left_deleted has been set to %true if @node's
|
||||
* in-order successor was the left child of @parent,
|
||||
* otherwise %false. */
|
||||
} else {
|
||||
struct iwavl_node *child;
|
||||
|
||||
/* @node is missing at least one child. Unlink it. Set
|
||||
* @parent to @node's parent, and set @left_deleted to
|
||||
* reflect which child of @parent @node was. Or, if
|
||||
* @node was the root node, simply update the root node
|
||||
* and return. */
|
||||
child = node->left ? node->left : node->right;
|
||||
parent = iwavl_get_parent(node);
|
||||
if (parent) {
|
||||
if (node == parent->left) {
|
||||
parent->left = child;
|
||||
left_deleted = true;
|
||||
} else {
|
||||
parent->right = child;
|
||||
left_deleted = false;
|
||||
}
|
||||
if (child) {
|
||||
avl_set_parent(child, parent);
|
||||
}
|
||||
} else {
|
||||
if (child) {
|
||||
avl_set_parent(child, parent);
|
||||
}
|
||||
*root_ptr = child;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Rebalance the tree. */
|
||||
do {
|
||||
if (left_deleted) {
|
||||
parent = avl_handle_subtree_shrink(root_ptr, parent,
|
||||
+1, &left_deleted);
|
||||
} else {
|
||||
parent = avl_handle_subtree_shrink(root_ptr, parent,
|
||||
-1, &left_deleted);
|
||||
}
|
||||
} while (parent);
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
* https://github.com/ebiggers/avl_tree
|
||||
*
|
||||
* iwavl.h - intrusive, nonrecursive AVL tree data structure (self-balancing
|
||||
* binary search tree), header file
|
||||
*
|
||||
* Written in 2014-2016 by Eric Biggers <ebiggers3@gmail.com>
|
||||
*
|
||||
* To the extent possible under law, the author(s) have dedicated all copyright
|
||||
* and related and neighboring rights to this software to the public domain
|
||||
* worldwide via the Creative Commons Zero 1.0 Universal Public Domain
|
||||
* Dedication (the "CC0").
|
||||
*
|
||||
* This software is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the CC0 for more details.
|
||||
*
|
||||
* You should have received a copy of the CC0 along with this software; if not
|
||||
* see <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||
*/
|
||||
|
||||
#ifndef IWAVL_H
|
||||
#define IWAVL_H
|
||||
|
||||
#include "basedefs.h"
|
||||
|
||||
/* Node in an AVL tree. Embed this in some other data structure. */
|
||||
struct iwavl_node {
|
||||
/* Pointer to left child or NULL */
|
||||
struct iwavl_node *left;
|
||||
|
||||
/* Pointer to right child or NULL */
|
||||
struct iwavl_node *right;
|
||||
|
||||
/* Pointer to parent combined with the balance factor. This saves 4 or
|
||||
* 8 bytes of memory depending on the CPU architecture.
|
||||
*
|
||||
* Low 2 bits: One greater than the balance factor of this subtree,
|
||||
* which is equal to height(right) - height(left). The mapping is:
|
||||
*
|
||||
* 00 => -1
|
||||
* 01 => 0
|
||||
* 10 => +1
|
||||
* 11 => undefined
|
||||
*
|
||||
* The rest of the bits are the pointer to the parent node. It must be
|
||||
* 4-byte aligned, and it will be NULL if this is the root node and
|
||||
* therefore has no parent. */
|
||||
uintptr_t parent_balance;
|
||||
};
|
||||
|
||||
/* Cast an AVL tree node to the containing data structure. */
|
||||
#define iwavl_entry(entry, type, member) \
|
||||
((type*) ((char*) (entry) - offsetof(type, member)))
|
||||
|
||||
/* Returns a pointer to the parent of the specified AVL tree node, or NULL if it
|
||||
* is already the root of the tree. */
|
||||
IW_INLINE struct iwavl_node* iwavl_get_parent(const struct iwavl_node *node) {
|
||||
return (struct iwavl_node*) (node->parent_balance & ~3);
|
||||
}
|
||||
|
||||
/* Marks the specified AVL tree node as unlinked from any tree. */
|
||||
IW_INLINE void iwavl_node_set_unlinked(struct iwavl_node *node) {
|
||||
node->parent_balance = (uintptr_t) node;
|
||||
}
|
||||
|
||||
/* Returns true iff the specified AVL tree node has been marked with
|
||||
* iwavl_node_set_unlinked() and has not subsequently been inserted into a
|
||||
* tree. */
|
||||
IW_INLINE bool iwavl_node_is_unlinked(const struct iwavl_node *node) {
|
||||
return node->parent_balance == (uintptr_t) node;
|
||||
}
|
||||
|
||||
/* (Internal use only) */
|
||||
extern void iwavl_rebalance_after_insert(
|
||||
struct iwavl_node **root_ptr,
|
||||
struct iwavl_node *inserted);
|
||||
|
||||
/*
|
||||
* Looks up an item in the specified AVL tree.
|
||||
*
|
||||
* @root
|
||||
* Pointer to the root of the AVL tree. (This can be NULL --- that just
|
||||
* means the tree is empty.)
|
||||
*
|
||||
* @cmp_ctx
|
||||
* First argument to pass to the comparison callback. This generally
|
||||
* should be a pointer to an object equal to the one being searched for.
|
||||
*
|
||||
* @cmp
|
||||
* Comparison callback. Must return < 0, 0, or > 0 if the first argument
|
||||
* is less than, equal to, or greater than the second argument,
|
||||
* respectively. The first argument will be @cmp_ctx and the second
|
||||
* argument will be a pointer to the AVL tree node of an item in the tree.
|
||||
*
|
||||
* Returns a pointer to the AVL tree node of the resulting item, or NULL if the
|
||||
* item was not found.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* struct int_wrapper {
|
||||
* int data;
|
||||
* struct iwavl_node index_node;
|
||||
* };
|
||||
*
|
||||
* static int _avl_cmp_int_to_node(const void *intptr,
|
||||
* const struct iwavl_node *nodeptr)
|
||||
* {
|
||||
* int n1 = *(const int *)intptr;
|
||||
* int n2 = iwavl_entry(nodeptr, struct int_wrapper, index_node)->data;
|
||||
* if (n1 < n2)
|
||||
* return -1;
|
||||
* else if (n1 > n2)
|
||||
* return 1;
|
||||
* else
|
||||
* return 0;
|
||||
* }
|
||||
*
|
||||
* bool contains_int(struct iwavl_node *root, int n)
|
||||
* {
|
||||
* struct iwavl_node *result;
|
||||
*
|
||||
* result = iwavl_lookup(root, &n, _avl_cmp_int_to_node);
|
||||
* return result ? true : false;
|
||||
* }
|
||||
*/
|
||||
IW_INLINE struct iwavl_node* iwavl_lookup(
|
||||
const struct iwavl_node *root,
|
||||
const void *cmp_ctx,
|
||||
int (*cmp)(const void*, const struct iwavl_node*)
|
||||
) {
|
||||
const struct iwavl_node *cur = root;
|
||||
|
||||
while (cur) {
|
||||
int res = (*cmp)(cmp_ctx, cur);
|
||||
if (res < 0) {
|
||||
cur = cur->left;
|
||||
} else if (res > 0) {
|
||||
cur = cur->right;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return (struct iwavl_node*) cur;
|
||||
}
|
||||
|
||||
IW_INLINE void iwavl_lookup_bounds(
|
||||
const struct iwavl_node *root,
|
||||
const void *cmp_ctx,
|
||||
int (*cmp)(const void*, const struct iwavl_node*),
|
||||
const struct iwavl_node **lb,
|
||||
const struct iwavl_node **ub
|
||||
) {
|
||||
*lb = *ub = 0;
|
||||
const struct iwavl_node *cur = root;
|
||||
|
||||
while (cur) {
|
||||
int res = (*cmp)(cmp_ctx, cur);
|
||||
if (res < 0) {
|
||||
*ub = cur;
|
||||
cur = cur->left;
|
||||
} else if (res > 0) {
|
||||
*lb = cur;
|
||||
cur = cur->right;
|
||||
} else {
|
||||
*lb = *ub = cur;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Same as iwavl_lookup(), but uses a more specific type for the comparison
|
||||
* function. Specifically, with this function the item being searched for is
|
||||
* expected to be in the same format as those already in the tree, with an
|
||||
* embedded 'struct iwavl_node'. */
|
||||
IW_INLINE struct iwavl_node* iwavl_lookup_node(
|
||||
const struct iwavl_node *root,
|
||||
const struct iwavl_node *node,
|
||||
int ( *cmp )(const struct iwavl_node*,
|
||||
const struct iwavl_node*)
|
||||
) {
|
||||
const struct iwavl_node *cur = root;
|
||||
|
||||
while (cur) {
|
||||
int res = (*cmp)(node, cur);
|
||||
if (res < 0) {
|
||||
cur = cur->left;
|
||||
} else if (res > 0) {
|
||||
cur = cur->right;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return (struct iwavl_node*) cur;
|
||||
}
|
||||
|
||||
/*
|
||||
* Inserts an item into the specified AVL tree.
|
||||
*
|
||||
* @root_ptr
|
||||
* Location of the AVL tree's root pointer. Indirection is needed because
|
||||
* the root node may change as a result of rotations caused by the
|
||||
* insertion. Initialize *root_ptr to NULL for an empty tree.
|
||||
*
|
||||
* @item
|
||||
* Pointer to the `struct iwavl_node' embedded in the item to insert.
|
||||
* No members in it need be pre-initialized, although members in the
|
||||
* containing structure should be pre-initialized so that @cmp can use them
|
||||
* in comparisons.
|
||||
*
|
||||
* @cmp
|
||||
* Comparison callback. Must return < 0, 0, or > 0 if the first argument
|
||||
* is less than, equal to, or greater than the second argument,
|
||||
* respectively. The first argument will be @item and the second
|
||||
* argument will be a pointer to an AVL tree node embedded in some
|
||||
* previously-inserted item to which @item is being compared.
|
||||
*
|
||||
* If no item in the tree is comparatively equal (via @cmp) to @item, inserts
|
||||
* @item and returns NULL. Otherwise does nothing and returns a pointer to the
|
||||
* AVL tree node embedded in the previously-inserted item which compared equal
|
||||
* to @item.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* struct int_wrapper {
|
||||
* int data;
|
||||
* struct iwavl_node index_node;
|
||||
* };
|
||||
*
|
||||
* #define GET_DATA(i) iwavl_entry((i), struct int_wrapper, index_node)->data
|
||||
*
|
||||
* static int _avl_cmp_ints(const struct iwavl_node *node1,
|
||||
* const struct iwavl_node *node2)
|
||||
* {
|
||||
* int n1 = GET_DATA(node1);
|
||||
* int n2 = GET_DATA(node2);
|
||||
* if (n1 < n2)
|
||||
* return -1;
|
||||
* else if (n1 > n2)
|
||||
* return 1;
|
||||
* else
|
||||
* return 0;
|
||||
* }
|
||||
*
|
||||
* bool insert_int(struct iwavl_node **root_ptr, int data)
|
||||
* {
|
||||
* struct int_wrapper *i = malloc(sizeof(struct int_wrapper));
|
||||
* i->data = data;
|
||||
* if (iwavl_insert(root_ptr, &i->index_node, _avl_cmp_ints)) {
|
||||
* // Duplicate.
|
||||
* free(i);
|
||||
* return false;
|
||||
* }
|
||||
* return true;
|
||||
* }
|
||||
*/
|
||||
IW_INLINE struct iwavl_node* iwavl_insert(
|
||||
struct iwavl_node **root_ptr,
|
||||
struct iwavl_node *item,
|
||||
int ( *cmp )(const struct iwavl_node*,
|
||||
const struct iwavl_node*)
|
||||
) {
|
||||
struct iwavl_node **cur_ptr = root_ptr, *cur = NULL;
|
||||
int res;
|
||||
|
||||
while (*cur_ptr) {
|
||||
cur = *cur_ptr;
|
||||
res = (*cmp)(item, cur);
|
||||
if (res < 0) {
|
||||
cur_ptr = &cur->left;
|
||||
} else if (res > 0) {
|
||||
cur_ptr = &cur->right;
|
||||
} else {
|
||||
return cur;
|
||||
}
|
||||
}
|
||||
*cur_ptr = item;
|
||||
item->parent_balance = (uintptr_t) cur | 1;
|
||||
iwavl_rebalance_after_insert(root_ptr, item);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Removes an item from the specified AVL tree.
|
||||
* See implementation for details. */
|
||||
extern void iwavl_remove(struct iwavl_node **root_ptr, struct iwavl_node *node);
|
||||
|
||||
/* Nonrecursive AVL tree traversal functions */
|
||||
|
||||
extern struct iwavl_node* iwavl_first_in_order(const struct iwavl_node *root);
|
||||
|
||||
extern struct iwavl_node* iwavl_last_in_order(const struct iwavl_node *root);
|
||||
|
||||
extern struct iwavl_node* iwavl_next_in_order(const struct iwavl_node *node);
|
||||
|
||||
extern struct iwavl_node* iwavl_prev_in_order(const struct iwavl_node *node);
|
||||
|
||||
extern struct iwavl_node* iwavl_first_in_postorder(const struct iwavl_node *root);
|
||||
|
||||
extern struct iwavl_node* iwavl_next_in_postorder(
|
||||
const struct iwavl_node *prev,
|
||||
const struct iwavl_node *prev_parent);
|
||||
|
||||
/*
|
||||
* Iterate through the nodes in an AVL tree in sorted order.
|
||||
* You may not modify the tree during the iteration.
|
||||
*
|
||||
* @child_struct
|
||||
* Variable that will receive a pointer to each struct inserted into the
|
||||
* tree.
|
||||
* @root
|
||||
* Root of the AVL tree.
|
||||
* @struct_name
|
||||
* Type of *child_struct.
|
||||
* @struct_member
|
||||
* Member of @struct_name type that is the AVL tree node.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* struct int_wrapper {
|
||||
* int data;
|
||||
* struct iwavl_node index_node;
|
||||
* };
|
||||
*
|
||||
* void print_ints(struct iwavl_node *root)
|
||||
* {
|
||||
* struct int_wrapper *i;
|
||||
*
|
||||
* iwavl_for_each_in_order(i, root, struct int_wrapper, index_node)
|
||||
* printf("%d\n", i->data);
|
||||
* }
|
||||
*/
|
||||
#define iwavl_for_each_in_order(child_struct, root, \
|
||||
struct_name, struct_member) \
|
||||
for (struct iwavl_node *_cur = \
|
||||
iwavl_first_in_order(root); \
|
||||
_cur && ((child_struct) = \
|
||||
iwavl_entry(_cur, struct_name, \
|
||||
struct_member), 1); \
|
||||
_cur = iwavl_next_in_order(_cur))
|
||||
|
||||
/*
|
||||
* Like iwavl_for_each_in_order(), but uses the reverse order.
|
||||
*/
|
||||
#define iwavl_for_each_in_reverse_order(child_struct, root, \
|
||||
struct_name, struct_member) \
|
||||
for (struct iwavl_node *_cur = \
|
||||
iwavl_last_in_order(root); \
|
||||
_cur && ((child_struct) = \
|
||||
iwavl_entry(_cur, struct_name, \
|
||||
struct_member), 1); \
|
||||
_cur = iwavl_prev_in_order(_cur))
|
||||
|
||||
/*
|
||||
* Like iwavl_for_each_in_order(), but iterates through the nodes in
|
||||
* postorder, so the current node may be deleted or freed.
|
||||
*/
|
||||
#define iwavl_for_each_in_postorder(child_struct, root, \
|
||||
struct_name, struct_member) \
|
||||
for ( struct iwavl_node *_cur = \
|
||||
iwavl_first_in_postorder(root), *_parent; \
|
||||
_cur && ((child_struct) = \
|
||||
iwavl_entry(_cur, struct_name, \
|
||||
struct_member), 1) \
|
||||
&& (_parent = iwavl_get_parent(_cur), 1); \
|
||||
_cur = iwavl_next_in_postorder(_cur, _parent))
|
||||
|
||||
#endif /* _AVL_TREE_H_ */
|
||||
+48
-15
@@ -4,9 +4,10 @@
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
|
||||
// mapping of ASCII characters to hex values
|
||||
const uint8_t ascii2hex[] = {
|
||||
static const uint8_t ascii2hex[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........
|
||||
@@ -59,7 +60,6 @@ size_t iwhex2bin(const char *hex, int hexlen, char *out, int max) {
|
||||
return vpos;
|
||||
}
|
||||
}
|
||||
;
|
||||
return vpos;
|
||||
}
|
||||
|
||||
@@ -67,8 +67,8 @@ char* iwbin2hex(
|
||||
char* const hex,
|
||||
const size_t hex_maxlen,
|
||||
const unsigned char* const bin,
|
||||
const size_t bin_len) {
|
||||
|
||||
const size_t bin_len
|
||||
) {
|
||||
size_t i = (size_t) 0U;
|
||||
unsigned int x;
|
||||
int b;
|
||||
@@ -107,9 +107,13 @@ int iwitoa(int64_t v, char *buf, int max) {
|
||||
}
|
||||
// sign stuff
|
||||
if (v < 0) {
|
||||
v = -v;
|
||||
ITOA_SZSTEP(1)
|
||||
* ptr++ = '-';
|
||||
if (IW_UNLIKELY(v == INT64_MIN)) {
|
||||
return snprintf(buf, max, "-9223372036854775808");
|
||||
} else {
|
||||
v = -v;
|
||||
ITOA_SZSTEP(1)
|
||||
* ptr++ = '-';
|
||||
}
|
||||
}
|
||||
// save start pointer
|
||||
p = ptr;
|
||||
@@ -136,7 +140,7 @@ int iwitoa(int64_t v, char *buf, int max) {
|
||||
#undef ITOA_SZSTEP
|
||||
}
|
||||
|
||||
char* iwftoa(long double n, char s[static IWFTOA_BUFSIZE]) {
|
||||
char* iwftoa(long double n, char s[static IWNUMBUF_SIZE]) {
|
||||
static double PRECISION = 0.00000000000001;
|
||||
// handle special cases
|
||||
if (isnan(n)) {
|
||||
@@ -238,6 +242,38 @@ int64_t iwatoi(const char *str) {
|
||||
return num * sign;
|
||||
}
|
||||
|
||||
int64_t iwatoi2(const char *str, size_t len) {
|
||||
while (len > 0 && *str > '\0' && *str <= ' ') {
|
||||
str++;
|
||||
len--;
|
||||
}
|
||||
if (len == 0) {
|
||||
return 0;
|
||||
}
|
||||
int sign = 1;
|
||||
int64_t num = 0;
|
||||
if (*str == '-') {
|
||||
str++;
|
||||
len--;
|
||||
sign = -1;
|
||||
} else if (*str == '+') {
|
||||
str++;
|
||||
len--;
|
||||
}
|
||||
if (!strcmp(str, "inf")) {
|
||||
return (INT64_MAX * sign);
|
||||
}
|
||||
while (len > 0 && *str != '\0') {
|
||||
if ((*str < '0') || (*str > '9')) {
|
||||
break;
|
||||
}
|
||||
num = num * 10 + *str - '0';
|
||||
str++;
|
||||
len--;
|
||||
}
|
||||
return num * sign;
|
||||
}
|
||||
|
||||
long double iwatof(const char *str) {
|
||||
assert(str);
|
||||
while (*str > '\0' && *str <= ' ') {
|
||||
@@ -345,8 +381,8 @@ int iwafcmp(const char *aptr, int asiz, const char *bptr, int bsiz) {
|
||||
if ((alen > 1) && (*arp == '.')) {
|
||||
arp++;
|
||||
alen--;
|
||||
if (alen > IWFTOA_BUFSIZE) {
|
||||
alen = IWFTOA_BUFSIZE;
|
||||
if (alen > IWNUMBUF_SIZE) {
|
||||
alen = IWNUMBUF_SIZE;
|
||||
}
|
||||
long double base = 10;
|
||||
while (alen > 0) {
|
||||
@@ -363,8 +399,8 @@ int iwafcmp(const char *aptr, int asiz, const char *bptr, int bsiz) {
|
||||
if ((blen > 1) && (*brp == '.')) {
|
||||
brp++;
|
||||
blen--;
|
||||
if (blen > IWFTOA_BUFSIZE) {
|
||||
blen = IWFTOA_BUFSIZE;
|
||||
if (blen > IWNUMBUF_SIZE) {
|
||||
blen = IWNUMBUF_SIZE;
|
||||
}
|
||||
long double base = 10;
|
||||
while (blen > 0) {
|
||||
@@ -407,7 +443,6 @@ static const char* skipwhite(const char *q) {
|
||||
double iwstrtod(const char *str, char **end) {
|
||||
double d = 0.0;
|
||||
int sign;
|
||||
int n = 0;
|
||||
const char *p, *a;
|
||||
|
||||
a = p = str;
|
||||
@@ -426,7 +461,6 @@ double iwstrtod(const char *str, char **end) {
|
||||
while (*p && isdigit(*p)) {
|
||||
d = d * 10.0 + (double) (*p - '0');
|
||||
++p;
|
||||
++n;
|
||||
}
|
||||
a = p;
|
||||
} else if (*p != '.') {
|
||||
@@ -445,7 +479,6 @@ double iwstrtod(const char *str, char **end) {
|
||||
f += base * (*p - '0');
|
||||
base /= 10.0;
|
||||
++p;
|
||||
++n;
|
||||
}
|
||||
}
|
||||
d += f * sign;
|
||||
|
||||
+3
-3
@@ -35,10 +35,10 @@
|
||||
|
||||
IW_EXTERN_C_START
|
||||
|
||||
#define IWFTOA_BUFSIZE 32
|
||||
|
||||
IW_EXPORT int64_t iwatoi(const char *str);
|
||||
|
||||
IW_EXPORT int64_t iwatoi2(const char *str, size_t len);
|
||||
|
||||
IW_EXPORT long double iwatof(const char *str);
|
||||
|
||||
IW_EXPORT double iwstrtod(const char *str, char **end);
|
||||
@@ -49,7 +49,7 @@ IW_EXPORT int iwitoa(int64_t v, char *buf, int max);
|
||||
* Convert a given floating point number to string.
|
||||
* @note Exponent notation can be used during conversion
|
||||
*/
|
||||
IW_EXPORT char* iwftoa(long double v, char buf[static IWFTOA_BUFSIZE]);
|
||||
IW_EXPORT char* iwftoa(long double v, char buf[static IWNUMBUF_SIZE]);
|
||||
|
||||
/**
|
||||
* Compare real(float) numbers encoded as decimal point string value.
|
||||
|
||||
+280
-70
@@ -10,9 +10,12 @@
|
||||
#define MIN_BUCKETS 64
|
||||
#define STEPS 4
|
||||
|
||||
typedef struct {
|
||||
void *key;
|
||||
void *val;
|
||||
struct lru_node;
|
||||
|
||||
typedef struct entry {
|
||||
void *key;
|
||||
void *val;
|
||||
struct lru_node *lru_node;
|
||||
uint32_t hash;
|
||||
} entry_t;
|
||||
|
||||
@@ -22,6 +25,12 @@ typedef struct {
|
||||
uint32_t total;
|
||||
} bucket_t;
|
||||
|
||||
typedef struct lru_node {
|
||||
struct lru_node *next;
|
||||
struct lru_node *prev;
|
||||
void *key;
|
||||
} lru_node_t;
|
||||
|
||||
typedef struct _IWHMAP {
|
||||
uint32_t count;
|
||||
uint32_t buckets_mask;
|
||||
@@ -31,12 +40,24 @@ typedef struct _IWHMAP {
|
||||
uint32_t (*hash_key_fn)(const void*);
|
||||
void (*kv_free_fn)(void*, void*);
|
||||
|
||||
// LRU
|
||||
struct lru_node *lru_first;
|
||||
struct lru_node *lru_last;
|
||||
iwhmap_lru_eviction_needed lru_ev;
|
||||
void *lru_ev_user_data;
|
||||
|
||||
bool int_key_as_pointer_value;
|
||||
} hmap_t;
|
||||
|
||||
static void _noop_kv_free(void *key, void *val) {
|
||||
}
|
||||
|
||||
static void _noop_uint64_kv_free(void *key, void *val) {
|
||||
if (key) {
|
||||
free(key);
|
||||
}
|
||||
}
|
||||
|
||||
void iwhmap_kv_free(void *key, void *val) {
|
||||
free(key);
|
||||
free(val);
|
||||
@@ -50,29 +71,29 @@ static int _ptr_cmp(const void *v1, const void *v2) {
|
||||
return v1 > v2 ? 1 : v1 < v2 ? -1 : 0;
|
||||
}
|
||||
|
||||
static int _int32_cmp(const void *v1, const void *v2) {
|
||||
static int _uint32_cmp(const void *v1, const void *v2) {
|
||||
intptr_t p1 = (intptr_t) v1;
|
||||
intptr_t p2 = (intptr_t) v2;
|
||||
return p1 > p2 ? 1 : p1 < p2 ? -1 : 0;
|
||||
}
|
||||
|
||||
static int _int64_cmp(const void *v1, const void *v2) {
|
||||
#ifdef IW_64
|
||||
intptr_t p1 = (intptr_t) v1;
|
||||
intptr_t p2 = (intptr_t) v2;
|
||||
return p1 > p2 ? 1 : p1 < p2 ? -1 : 0;
|
||||
#else
|
||||
int64_t l1, l2;
|
||||
memcpy(&l1, v1, sizeof(l1));
|
||||
memcpy(&l2, v2, sizeof(l2));
|
||||
return l1 > l2 ? 1 : l1 < l2 ? -1 : 0;
|
||||
#endif
|
||||
static int _uint64_cmp(const void *v1, const void *v2) {
|
||||
if (sizeof(uintptr_t) >= sizeof(uint64_t)) {
|
||||
intptr_t p1 = (intptr_t) v1;
|
||||
intptr_t p2 = (intptr_t) v2;
|
||||
return p1 > p2 ? 1 : p1 < p2 ? -1 : 0;
|
||||
} else {
|
||||
uint64_t l1, l2;
|
||||
memcpy(&l1, v1, sizeof(l1));
|
||||
memcpy(&l2, v2, sizeof(l2));
|
||||
return l1 > l2 ? 1 : l1 < l2 ? -1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
// https://gist.github.com/badboy/6267743
|
||||
// https://nullprogram.com/blog/2018/07/31
|
||||
|
||||
IW_INLINE uint32_t _hash_int32(uint32_t x) {
|
||||
IW_INLINE uint32_t _hash_uint32(uint32_t x) {
|
||||
x ^= x >> 17;
|
||||
x *= UINT32_C(0xed5ad4bb);
|
||||
x ^= x >> 11;
|
||||
@@ -83,22 +104,22 @@ IW_INLINE uint32_t _hash_int32(uint32_t x) {
|
||||
return x;
|
||||
}
|
||||
|
||||
IW_INLINE uint32_t _hash_int64(uint64_t x) {
|
||||
return _hash_int32(x) ^ _hash_int32(x >> 31);
|
||||
IW_INLINE uint32_t _hash_uint64(uint64_t x) {
|
||||
return _hash_uint32(x) ^ _hash_uint32(x >> 31);
|
||||
}
|
||||
|
||||
IW_INLINE uint32_t _hash_int64_key(const void *key) {
|
||||
#ifdef IW_64
|
||||
return _hash_int64((uint64_t) key);
|
||||
#else
|
||||
uint64_t lv;
|
||||
memcpy(&lv, key, sizeof(lv));
|
||||
return _hash_int64(lv);
|
||||
#endif
|
||||
IW_INLINE uint32_t _hash_uint64_key(const void *key) {
|
||||
if (sizeof(uintptr_t) >= sizeof(uint64_t)) {
|
||||
return _hash_uint64((uint64_t) key);
|
||||
} else {
|
||||
uint64_t lv;
|
||||
memcpy(&lv, key, sizeof(lv));
|
||||
return _hash_uint64(lv);
|
||||
}
|
||||
}
|
||||
|
||||
IW_INLINE uint32_t _hash_int32_key(const void *key) {
|
||||
return _hash_int32((uintptr_t) key);
|
||||
IW_INLINE uint32_t _hash_uint32_key(const void *key) {
|
||||
return _hash_uint32((uintptr_t) key);
|
||||
}
|
||||
|
||||
IW_INLINE uint32_t _hash_buf_key(const void *key) {
|
||||
@@ -108,8 +129,8 @@ IW_INLINE uint32_t _hash_buf_key(const void *key) {
|
||||
IWHMAP* iwhmap_create(
|
||||
int (*cmp_fn)(const void*, const void*),
|
||||
uint32_t (*hash_key_fn)(const void*),
|
||||
void (*kv_free_fn)(void*, void*)) {
|
||||
|
||||
void (*kv_free_fn)(void*, void*)
|
||||
) {
|
||||
if (!hash_key_fn) {
|
||||
return 0;
|
||||
}
|
||||
@@ -134,22 +155,28 @@ IWHMAP* iwhmap_create(
|
||||
hm->kv_free_fn = kv_free_fn;
|
||||
hm->buckets_mask = MIN_BUCKETS - 1;
|
||||
hm->count = 0;
|
||||
hm->lru_first = hm->lru_last = 0;
|
||||
hm->lru_ev = 0;
|
||||
hm->lru_ev_user_data = 0;
|
||||
hm->int_key_as_pointer_value = false;
|
||||
return hm;
|
||||
}
|
||||
|
||||
IWHMAP* iwhmap_create_i64(void (*kv_free_fn)(void*, void*)) {
|
||||
hmap_t *hm = iwhmap_create(_int64_cmp, _hash_int64_key, kv_free_fn);
|
||||
IWHMAP* iwhmap_create_u64(void (*kv_free_fn)(void*, void*)) {
|
||||
if (!kv_free_fn) {
|
||||
kv_free_fn = _noop_uint64_kv_free;
|
||||
}
|
||||
hmap_t *hm = iwhmap_create(_uint64_cmp, _hash_uint64_key, kv_free_fn);
|
||||
if (hm) {
|
||||
#ifdef IW_64
|
||||
hm->int_key_as_pointer_value = true;
|
||||
#endif
|
||||
if (sizeof(uintptr_t) >= sizeof(uint64_t)) {
|
||||
hm->int_key_as_pointer_value = true;
|
||||
}
|
||||
}
|
||||
return hm;
|
||||
}
|
||||
|
||||
IWHMAP* iwhmap_create_i32(void (*kv_free_fn)(void*, void*)) {
|
||||
hmap_t *hm = iwhmap_create(_int32_cmp, _hash_int32_key, kv_free_fn);
|
||||
IWHMAP* iwhmap_create_u32(void (*kv_free_fn)(void*, void*)) {
|
||||
hmap_t *hm = iwhmap_create(_uint32_cmp, _hash_uint32_key, kv_free_fn);
|
||||
if (hm) {
|
||||
hm->int_key_as_pointer_value = true;
|
||||
}
|
||||
@@ -164,7 +191,7 @@ static entry_t* _entry_find(IWHMAP *hm, const void *key, uint32_t hash) {
|
||||
bucket_t *bucket = hm->buckets + (hash & hm->buckets_mask);
|
||||
entry_t *entry = bucket->entries;
|
||||
for (entry_t *end = entry + bucket->used; entry < end; ++entry) {
|
||||
if ((hash == entry->hash) && (hm->cmp_fn(key, entry->key) == 0)) {
|
||||
if (hash == entry->hash && hm->cmp_fn(key, entry->key) == 0) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
@@ -201,6 +228,7 @@ static entry_t* _entry_add(IWHMAP *hm, void *key, uint32_t hash) {
|
||||
entry->hash = hash;
|
||||
entry->key = 0;
|
||||
entry->val = 0;
|
||||
entry->lru_node = 0;
|
||||
|
||||
return entry;
|
||||
}
|
||||
@@ -223,14 +251,17 @@ static void _rehash(hmap_t *hm, uint32_t num_buckets) {
|
||||
|
||||
for (bucket = hm->buckets; bucket < bucket_end; ++bucket) {
|
||||
entry_t *entry_old = bucket->entries;
|
||||
entry_t *entry_old_end = entry_old + bucket->used;
|
||||
for ( ; entry_old < entry_old_end; ++entry_old) {
|
||||
entry_t *entry_new = _entry_add(&hm_copy, entry_old->key, entry_old->hash);
|
||||
if (!entry_new) {
|
||||
goto fail;
|
||||
if (entry_old) {
|
||||
entry_t *entry_old_end = entry_old + bucket->used;
|
||||
for ( ; entry_old < entry_old_end; ++entry_old) {
|
||||
entry_t *entry_new = _entry_add(&hm_copy, entry_old->key, entry_old->hash);
|
||||
if (!entry_new) {
|
||||
goto fail;
|
||||
}
|
||||
entry_new->key = entry_old->key;
|
||||
entry_new->val = entry_old->val;
|
||||
entry_new->lru_node = entry_old->lru_node;
|
||||
}
|
||||
entry_new->key = entry_old->key;
|
||||
entry_new->val = entry_old->val;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,41 +283,74 @@ fail:
|
||||
free(buckets);
|
||||
}
|
||||
|
||||
iwrc iwhmap_put(IWHMAP *hm, void *key, void *val) {
|
||||
uint32_t hash = hm->hash_key_fn(key);
|
||||
entry_t *entry = _entry_add(hm, key, hash);
|
||||
if (!entry) {
|
||||
return iwrc_set_errno(IW_ERROR_ERRNO, errno);
|
||||
static void _lru_entry_update(IWHMAP *hm, entry_t *entry) {
|
||||
if (entry->lru_node) {
|
||||
entry->lru_node->key = entry->key;
|
||||
if (entry->lru_node->next) {
|
||||
struct lru_node *prev = entry->lru_node->prev;
|
||||
if (prev) {
|
||||
prev->next = entry->lru_node->next;
|
||||
} else {
|
||||
hm->lru_first = entry->lru_node->next;
|
||||
}
|
||||
entry->lru_node->next->prev = prev;
|
||||
hm->lru_last->next = entry->lru_node;
|
||||
entry->lru_node->next = 0;
|
||||
entry->lru_node->prev = hm->lru_last;
|
||||
hm->lru_last = entry->lru_node;
|
||||
}
|
||||
} else {
|
||||
entry->lru_node = malloc(sizeof(*entry->lru_node));
|
||||
if (entry->lru_node) {
|
||||
entry->lru_node->key = entry->key;
|
||||
if (hm->lru_last) {
|
||||
hm->lru_last->next = entry->lru_node;
|
||||
entry->lru_node->next = 0;
|
||||
entry->lru_node->prev = hm->lru_last;
|
||||
hm->lru_last = entry->lru_node;
|
||||
} else {
|
||||
hm->lru_first = hm->lru_last = entry->lru_node;
|
||||
entry->lru_node->next = entry->lru_node->prev = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hm->kv_free_fn(hm->int_key_as_pointer_value ? 0 : entry->key, entry->val);
|
||||
|
||||
entry->key = key;
|
||||
entry->val = val;
|
||||
|
||||
if (hm->count > hm->buckets_mask) {
|
||||
_rehash(hm, _n_buckets(hm) * 2);
|
||||
static void _lru_entry_remove(IWHMAP *hm, entry_t *entry) {
|
||||
if (entry->lru_node->next) {
|
||||
struct lru_node *prev = entry->lru_node->prev;
|
||||
if (prev) {
|
||||
prev->next = entry->lru_node->next;
|
||||
} else {
|
||||
hm->lru_first = entry->lru_node->next;
|
||||
}
|
||||
entry->lru_node->next->prev = prev;
|
||||
} else if (entry->lru_node->prev) {
|
||||
entry->lru_node->prev->next = 0;
|
||||
hm->lru_last = entry->lru_node->prev;
|
||||
} else {
|
||||
hm->lru_last = hm->lru_first = 0;
|
||||
}
|
||||
return 0;
|
||||
free(entry->lru_node);
|
||||
entry->lru_node = 0;
|
||||
}
|
||||
|
||||
void* iwhmap_get(IWHMAP *hm, const void *key) {
|
||||
uint32_t hash = hm->hash_key_fn(key);
|
||||
entry_t *entry = _entry_find(hm, key, hash);
|
||||
if (entry) {
|
||||
if (hm->lru_ev) {
|
||||
_lru_entry_update(hm, entry);
|
||||
}
|
||||
return entry->val;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void iwhmap_remove(IWHMAP *hm, const void *key) {
|
||||
uint32_t hash = hm->hash_key_fn(key);
|
||||
bucket_t *bucket = hm->buckets + (hash & hm->buckets_mask);
|
||||
|
||||
entry_t *entry = _entry_find(hm, key, hash);
|
||||
if (!entry) {
|
||||
return;
|
||||
static void _entry_remove(IWHMAP *hm, bucket_t *bucket, entry_t *entry) {
|
||||
if (entry->lru_node) {
|
||||
_lru_entry_remove(hm, entry);
|
||||
}
|
||||
|
||||
hm->kv_free_fn(hm->int_key_as_pointer_value ? 0 : entry->key, entry->val);
|
||||
@@ -305,7 +369,6 @@ void iwhmap_remove(IWHMAP *hm, const void *key) {
|
||||
} else {
|
||||
uint32_t steps_used = bucket->used / STEPS;
|
||||
uint32_t steps_total = bucket->total / STEPS;
|
||||
|
||||
if (steps_used + 1 < steps_total) {
|
||||
entry_t *entries_new = realloc(bucket->entries, (steps_used + 1) * STEPS * sizeof(entries_new[0]));
|
||||
if (entries_new) {
|
||||
@@ -316,7 +379,134 @@ void iwhmap_remove(IWHMAP *hm, const void *key) {
|
||||
}
|
||||
}
|
||||
|
||||
int iwhmap_count(IWHMAP *hm) {
|
||||
bool iwhmap_remove(IWHMAP *hm, const void *key) {
|
||||
uint32_t hash = hm->hash_key_fn(key);
|
||||
bucket_t *bucket = hm->buckets + (hash & hm->buckets_mask);
|
||||
entry_t *entry = _entry_find(hm, key, hash);
|
||||
if (entry) {
|
||||
_entry_remove(hm, bucket, entry);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool iwhmap_remove_u64(IWHMAP *hm, uint64_t key) {
|
||||
if (hm->int_key_as_pointer_value) {
|
||||
return iwhmap_remove(hm, (void*) (uintptr_t) key);
|
||||
} else {
|
||||
return iwhmap_remove(hm, &key);
|
||||
}
|
||||
}
|
||||
|
||||
bool iwhmap_remove_u32(IWHMAP *hm, uint32_t key) {
|
||||
return iwhmap_remove(hm, (void*) (uintptr_t) key);
|
||||
}
|
||||
|
||||
iwrc iwhmap_put(IWHMAP *hm, void *key, void *val) {
|
||||
uint32_t hash = hm->hash_key_fn(key);
|
||||
entry_t *entry = _entry_add(hm, key, hash);
|
||||
if (!entry) {
|
||||
return iwrc_set_errno(IW_ERROR_ERRNO, errno);
|
||||
}
|
||||
|
||||
hm->kv_free_fn(hm->int_key_as_pointer_value ? 0 : entry->key, entry->val);
|
||||
|
||||
entry->key = key;
|
||||
entry->val = val;
|
||||
|
||||
if (hm->lru_ev) {
|
||||
_lru_entry_update(hm, entry);
|
||||
}
|
||||
|
||||
if (hm->count > hm->buckets_mask) {
|
||||
_rehash(hm, _n_buckets(hm) * 2);
|
||||
}
|
||||
|
||||
while (hm->lru_first && hm->lru_ev(hm, hm->lru_ev_user_data)) {
|
||||
hash = hm->hash_key_fn(hm->lru_first->key);
|
||||
bucket_t *bucket = hm->buckets + (hash & hm->buckets_mask);
|
||||
entry = _entry_find(hm, hm->lru_first->key, hash);
|
||||
assert(entry); // Should never be zero.
|
||||
_entry_remove(hm, bucket, entry);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
iwrc iwhmap_put_str(IWHMAP *hm, const char *key_, void *val) {
|
||||
char *key = strdup(key_);
|
||||
if (!key) {
|
||||
return iwrc_set_errno(IW_ERROR_ALLOC, errno);
|
||||
}
|
||||
iwrc rc = iwhmap_put(hm, key, val);
|
||||
if (rc) {
|
||||
free(key);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
iwrc iwhmap_rename(IWHMAP *hm, const void *key_old, void *key_new) {
|
||||
uint32_t hash = hm->hash_key_fn(key_old);
|
||||
entry_t *entry = _entry_find(hm, key_old, hash);
|
||||
bucket_t *bucket = hm->buckets + (hash & hm->buckets_mask);
|
||||
|
||||
if (entry) {
|
||||
void *val = entry->val;
|
||||
entry->val = 0;
|
||||
_entry_remove(hm, bucket, entry);
|
||||
hash = hm->hash_key_fn(key_new);
|
||||
entry = _entry_add(hm, key_new, hash);
|
||||
if (!entry) {
|
||||
return iwrc_set_errno(IW_ERROR_ERRNO, errno);
|
||||
}
|
||||
hm->kv_free_fn(hm->int_key_as_pointer_value ? 0 : entry->key, entry->val);
|
||||
|
||||
entry->key = key_new;
|
||||
entry->val = val;
|
||||
|
||||
if (hm->lru_ev) {
|
||||
_lru_entry_update(hm, entry);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
iwrc iwhmap_put_u32(IWHMAP *hm, uint32_t key, void *val) {
|
||||
return iwhmap_put(hm, (void*) (uintptr_t) key, val);
|
||||
}
|
||||
|
||||
iwrc iwhmap_put_u64(IWHMAP *hm, uint64_t key, void *val) {
|
||||
if (hm->int_key_as_pointer_value) {
|
||||
return iwhmap_put(hm, (void*) (uintptr_t) key, val);
|
||||
} else {
|
||||
uint64_t *kv = malloc(sizeof(*kv));
|
||||
if (!kv) {
|
||||
return iwrc_set_errno(IW_ERROR_ALLOC, errno);
|
||||
}
|
||||
memcpy(kv, &key, sizeof(*kv));
|
||||
iwrc rc = iwhmap_put(hm, kv, val);
|
||||
if (rc) {
|
||||
free(kv);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
void* iwhmap_get_u64(IWHMAP *hm, uint64_t key) {
|
||||
if (hm->int_key_as_pointer_value) {
|
||||
return iwhmap_get(hm, (void*) (intptr_t) key);
|
||||
} else {
|
||||
return iwhmap_get(hm, &key);
|
||||
}
|
||||
}
|
||||
|
||||
void* iwhmap_get_u32(IWHMAP *hm, uint32_t key) {
|
||||
return iwhmap_get(hm, (void*) (intptr_t) key);
|
||||
}
|
||||
|
||||
uint32_t iwhmap_count(IWHMAP *hm) {
|
||||
return hm->count;
|
||||
}
|
||||
|
||||
@@ -329,6 +519,9 @@ void iwhmap_iter_init(IWHMAP *hm, IWHMAP_ITER *iter) {
|
||||
}
|
||||
|
||||
bool iwhmap_iter_next(IWHMAP_ITER *iter) {
|
||||
if (!iter->hm) {
|
||||
return false;
|
||||
}
|
||||
entry_t *entry;
|
||||
bucket_t *bucket = iter->hm->buckets + iter->bucket;
|
||||
|
||||
@@ -381,11 +574,28 @@ void iwhmap_destroy(IWHMAP *hm) {
|
||||
return;
|
||||
}
|
||||
for (bucket_t *b = hm->buckets, *be = hm->buckets + _n_buckets(hm); b < be; ++b) {
|
||||
for (entry_t *e = b->entries, *ee = b->entries + b->used; e < ee; ++e) {
|
||||
hm->kv_free_fn(hm->int_key_as_pointer_value ? 0 : e->key, e->val);
|
||||
if (b->entries) {
|
||||
for (entry_t *e = b->entries, *ee = b->entries + b->used; e < ee; ++e) {
|
||||
hm->kv_free_fn(hm->int_key_as_pointer_value ? 0 : e->key, e->val);
|
||||
}
|
||||
free(b->entries);
|
||||
}
|
||||
free(b->entries);
|
||||
}
|
||||
for (lru_node_t *n = hm->lru_first; n; ) {
|
||||
lru_node_t *nn = n->next;
|
||||
free(n);
|
||||
n = nn;
|
||||
}
|
||||
free(hm->buckets);
|
||||
free(hm);
|
||||
}
|
||||
|
||||
bool iwhmap_lru_eviction_max_count(IWHMAP *hm, void *max_count_val) {
|
||||
uint32_t max_count = (uintptr_t) max_count_val;
|
||||
return iwhmap_count(hm) > max_count;
|
||||
}
|
||||
|
||||
void iwhmap_lru_init(IWHMAP *hm, iwhmap_lru_eviction_needed ev, void *ev_user_data) {
|
||||
hm->lru_ev = ev;
|
||||
hm->lru_ev_user_data = ev_user_data;
|
||||
}
|
||||
|
||||
+33
-4
@@ -57,19 +57,39 @@ IW_EXPORT IWHMAP* iwhmap_create(
|
||||
uint32_t (*hash_key_fn)(const void*),
|
||||
void (*kv_free_fn)(void*, void*));
|
||||
|
||||
IW_EXPORT IWHMAP* iwhmap_create_i64(void (*kv_free_fn)(void*, void*));
|
||||
IW_EXPORT IWHMAP* iwhmap_create_ptr(void (*kv_free_fn)(void*, void*));
|
||||
|
||||
IW_EXPORT IWHMAP* iwhmap_create_i32(void (*kv_free_fn)(void*, void*));
|
||||
IW_EXPORT IWHMAP* iwhmap_create_u64(void (*kv_free_fn)(void*, void*));
|
||||
|
||||
IW_EXPORT IWHMAP* iwhmap_create_u32(void (*kv_free_fn)(void*, void*));
|
||||
|
||||
IW_EXPORT IWHMAP* iwhmap_create_str(void (*kv_free_fn)(void*, void*));
|
||||
|
||||
IW_EXPORT iwrc iwhmap_put(IWHMAP *hm, void *key, void *val);
|
||||
|
||||
IW_EXPORT void iwhmap_remove(IWHMAP *hm, const void *key);
|
||||
IW_EXPORT iwrc iwhmap_put_u32(IWHMAP *hm, uint32_t key, void *val);
|
||||
|
||||
IW_EXPORT iwrc iwhmap_put_u64(IWHMAP *hm, uint64_t key, void *val);
|
||||
|
||||
/// Makes copy of key (strdup) then puts key value pair into map.
|
||||
/// @note Key memory expected to be released by `kv_free_fn` function given to iwhmap_create_xxx.
|
||||
IW_EXPORT iwrc iwhmap_put_str(IWHMAP *hm, const char *key, void *val);
|
||||
|
||||
IW_EXPORT iwrc iwhmap_rename(IWHMAP *hm, const void *key_old, void *key_new);
|
||||
|
||||
IW_EXPORT bool iwhmap_remove(IWHMAP *hm, const void *key);
|
||||
|
||||
IW_EXPORT bool iwhmap_remove_u64(IWHMAP *hm, uint64_t key);
|
||||
|
||||
IW_EXPORT bool iwhmap_remove_u32(IWHMAP *hm, uint32_t key);
|
||||
|
||||
IW_EXPORT void* iwhmap_get(IWHMAP *hm, const void *key);
|
||||
|
||||
IW_EXPORT int iwhmap_count(IWHMAP *hm);
|
||||
IW_EXPORT void* iwhmap_get_u64(IWHMAP *hm, uint64_t key);
|
||||
|
||||
IW_EXPORT void* iwhmap_get_u32(IWHMAP *hm, uint32_t key);
|
||||
|
||||
IW_EXPORT uint32_t iwhmap_count(IWHMAP *hm);
|
||||
|
||||
IW_EXPORT void iwhmap_clear(IWHMAP *hm);
|
||||
|
||||
@@ -79,5 +99,14 @@ IW_EXPORT bool iwhmap_iter_next(IWHMAP_ITER *iter);
|
||||
|
||||
IW_EXPORT void iwhmap_destroy(IWHMAP *hm);
|
||||
|
||||
typedef bool (*iwhmap_lru_eviction_needed)(IWHMAP *hm, void *user_data);
|
||||
|
||||
IW_EXPORT bool iwhmap_lru_eviction_max_count(IWHMAP *hm, void *max_count_val);
|
||||
|
||||
/// Init LRU eviction mode for given `hm` map.
|
||||
/// @param ev Returns `true` if needed to evict the next least recently used element.
|
||||
/// @param ev_user_data Arbitrary user data from `ev` function.
|
||||
IW_EXPORT void iwhmap_lru_init(IWHMAP *hm, iwhmap_lru_eviction_needed ev, void *ev_user_data);
|
||||
|
||||
IW_EXTERN_C_END
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,307 @@
|
||||
/* inih -- simple .INI file parser
|
||||
|
||||
SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
Copyright (C) 2009-2020, Ben Hoyt
|
||||
|
||||
inih is released under the New BSD license (see LICENSE.txt). Go to the project
|
||||
home page for more info:
|
||||
|
||||
https://github.com/benhoyt/inih
|
||||
|
||||
*/
|
||||
|
||||
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "iwini.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
#if !IWINI_USE_STACK
|
||||
#if IWINI_CUSTOM_ALLOCATOR
|
||||
#include <stddef.h>
|
||||
void* iwini_malloc(size_t size);
|
||||
void iwini_free(void *ptr);
|
||||
void* iwini_realloc(void *ptr, size_t size);
|
||||
|
||||
#else
|
||||
#include <stdlib.h>
|
||||
#define iwini_malloc malloc
|
||||
#define iwini_free free
|
||||
#define iwini_realloc realloc
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define MAX_SECTION 127
|
||||
#define MAX_NAME 127
|
||||
|
||||
/* Used by ini_parse_string() to keep track of string parsing state. */
|
||||
typedef struct {
|
||||
const char *ptr;
|
||||
size_t num_left;
|
||||
} ini_parse_string_ctx;
|
||||
|
||||
/* Strip whitespace chars off end of given string, in place. Return s. */
|
||||
static char* rstrip(char *s) {
|
||||
char *p = s + strlen(s);
|
||||
while (p > s && isspace((unsigned char) (*--p))) {
|
||||
*p = '\0';
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Return pointer to first non-whitespace char in given string. */
|
||||
static char* lskip(const char *s) {
|
||||
while (*s && isspace((unsigned char) (*s))) {
|
||||
s++;
|
||||
}
|
||||
return (char*) s;
|
||||
}
|
||||
|
||||
/* Return pointer to first char (of chars) or inline comment in given string,
|
||||
or pointer to NUL at end of string if neither found. Inline comment must
|
||||
be prefixed by a whitespace character to register as a comment. */
|
||||
static char* find_chars_or_comment(const char *s, const char *chars) {
|
||||
#if IWINI_ALLOW_INLINE_COMMENTS
|
||||
int was_space = 0;
|
||||
while ( *s && (!chars || !strchr(chars, *s))
|
||||
&& !(was_space && strchr(IWINI_INLINE_COMMENT_PREFIXES, *s))) {
|
||||
was_space = isspace((unsigned char) (*s));
|
||||
s++;
|
||||
}
|
||||
#else
|
||||
while (*s && (!chars || !strchr(chars, *s))) {
|
||||
s++;
|
||||
}
|
||||
#endif
|
||||
return (char*) s;
|
||||
}
|
||||
|
||||
/* Similar to strncpy, but ensures dest (size bytes) is
|
||||
NUL-terminated, and doesn't pad with NULs. */
|
||||
static char* strncpy0(char *dest, const char *src, size_t size) {
|
||||
/* Could use strncpy internally, but it causes gcc warnings (see issue #91) */
|
||||
size_t i;
|
||||
for (i = 0; i < size - 1 && src[i]; i++) {
|
||||
dest[i] = src[i];
|
||||
}
|
||||
dest[i] = '\0';
|
||||
return dest;
|
||||
}
|
||||
|
||||
/* See documentation in header file. */
|
||||
int iwini_parse_stream(
|
||||
iwini_reader reader, void *stream, iwini_handler handler,
|
||||
void *user
|
||||
) {
|
||||
/* Uses a fair bit of stack (use heap instead if you need to) */
|
||||
#if IWINI_USE_STACK
|
||||
char line[IWINI_MAX_LINE];
|
||||
int max_line = IWINI_MAX_LINE;
|
||||
#else
|
||||
char *line;
|
||||
size_t max_line = IWINI_INITIAL_ALLOC;
|
||||
#endif
|
||||
#if IWINI_ALLOW_REALLOC && !IWINI_USE_STACK
|
||||
char *new_line;
|
||||
size_t offset;
|
||||
#endif
|
||||
char section[MAX_SECTION] = "";
|
||||
char prev_name[MAX_NAME] = "";
|
||||
|
||||
char *start;
|
||||
char *end;
|
||||
char *name;
|
||||
char *value;
|
||||
int lineno = 0;
|
||||
int error = 0;
|
||||
|
||||
#if !IWINI_USE_STACK
|
||||
line = (char*) iwini_malloc(IWINI_INITIAL_ALLOC);
|
||||
if (!line) {
|
||||
return -2;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if IWINI_HANDLER_LINENO
|
||||
#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno)
|
||||
#else
|
||||
#define HANDLER(u, s, n, v) handler(u, s, n, v)
|
||||
#endif
|
||||
|
||||
/* Scan through stream line by line */
|
||||
while (reader(line, max_line, stream) != NULL) {
|
||||
#if IWINI_ALLOW_REALLOC && !IWINI_USE_STACK
|
||||
offset = strlen(line);
|
||||
while (offset == max_line - 1 && line[offset - 1] != '\n') {
|
||||
max_line *= 2;
|
||||
if (max_line > IWINI_MAX_LINE) {
|
||||
max_line = IWINI_MAX_LINE;
|
||||
}
|
||||
new_line = iwini_realloc(line, max_line);
|
||||
if (!new_line) {
|
||||
iwini_free(line);
|
||||
return -2;
|
||||
}
|
||||
line = new_line;
|
||||
if (reader(line + offset, (int) (max_line - offset), stream) == NULL) {
|
||||
break;
|
||||
}
|
||||
if (max_line >= IWINI_MAX_LINE) {
|
||||
break;
|
||||
}
|
||||
offset += strlen(line + offset);
|
||||
}
|
||||
#endif
|
||||
|
||||
lineno++;
|
||||
|
||||
start = line;
|
||||
#if IWINI_ALLOW_BOM
|
||||
if ( (lineno == 1) && ((unsigned char) start[0] == 0xEF)
|
||||
&& ((unsigned char) start[1] == 0xBB)
|
||||
&& ((unsigned char) start[2] == 0xBF)) {
|
||||
start += 3;
|
||||
}
|
||||
#endif
|
||||
start = lskip(rstrip(start));
|
||||
|
||||
if (strchr(IWINI_START_COMMENT_PREFIXES, *start)) {
|
||||
/* Start-of-line comment */
|
||||
}
|
||||
#if IWINI_ALLOW_MULTILINE
|
||||
else if (*prev_name && *start && (start > line)) {
|
||||
/* Non-blank line with leading whitespace, treat as continuation
|
||||
of previous name's value (as per Python configparser). */
|
||||
if (!HANDLER(user, section, prev_name, start) && !error) {
|
||||
error = lineno;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else if (*start == '[') {
|
||||
/* A "[section]" line */
|
||||
end = find_chars_or_comment(start + 1, "]");
|
||||
if (*end == ']') {
|
||||
*end = '\0';
|
||||
strncpy0(section, start + 1, sizeof(section));
|
||||
*prev_name = '\0';
|
||||
#if IWINI_CALL_HANDLER_ON_NEW_SECTION
|
||||
if (!HANDLER(user, section, NULL, NULL) && !error) {
|
||||
error = lineno;
|
||||
}
|
||||
#endif
|
||||
} else if (!error) {
|
||||
/* No ']' found on section line */
|
||||
error = lineno;
|
||||
}
|
||||
} else if (*start) {
|
||||
/* Not a comment, must be a name[=:]value pair */
|
||||
end = find_chars_or_comment(start, "=:");
|
||||
if ((*end == '=') || (*end == ':')) {
|
||||
*end = '\0';
|
||||
name = rstrip(start);
|
||||
value = end + 1;
|
||||
#if IWINI_ALLOW_INLINE_COMMENTS
|
||||
end = find_chars_or_comment(value, NULL);
|
||||
if (*end) {
|
||||
*end = '\0';
|
||||
}
|
||||
#endif
|
||||
value = lskip(value);
|
||||
rstrip(value);
|
||||
|
||||
/* Valid name[=:]value pair found, call handler */
|
||||
strncpy0(prev_name, name, sizeof(prev_name));
|
||||
if (!HANDLER(user, section, name, value) && !error) {
|
||||
error = lineno;
|
||||
}
|
||||
} else if (!error) {
|
||||
/* No '=' or ':' found on name[=:]value line */
|
||||
#if IWINI_ALLOW_NO_VALUE
|
||||
*end = '\0';
|
||||
name = rstrip(start);
|
||||
if (!HANDLER(user, section, name, NULL) && !error) {
|
||||
error = lineno;
|
||||
}
|
||||
#else
|
||||
error = lineno;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if IWINI_STOP_ON_FIRST_ERROR
|
||||
if (error) {
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !IWINI_USE_STACK
|
||||
iwini_free(line);
|
||||
#endif
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/* See documentation in header file. */
|
||||
int iwini_parse_file(FILE *file, iwini_handler handler, void *user) {
|
||||
return iwini_parse_stream((iwini_reader) fgets, file, handler, user);
|
||||
}
|
||||
|
||||
/* See documentation in header file. */
|
||||
int iwini_parse(const char *filename, iwini_handler handler, void *user) {
|
||||
FILE *file;
|
||||
int error;
|
||||
|
||||
file = fopen(filename, "r");
|
||||
if (!file) {
|
||||
return -1;
|
||||
}
|
||||
error = iwini_parse_file(file, handler, user);
|
||||
fclose(file);
|
||||
return error;
|
||||
}
|
||||
|
||||
/* An ini_reader function to read the next line from a string buffer. This
|
||||
is the fgets() equivalent used by ini_parse_string(). */
|
||||
static char* ini_reader_string(char *str, int num, void *stream) {
|
||||
ini_parse_string_ctx *ctx = (ini_parse_string_ctx*) stream;
|
||||
const char *ctx_ptr = ctx->ptr;
|
||||
size_t ctx_num_left = ctx->num_left;
|
||||
char *strp = str;
|
||||
char c;
|
||||
|
||||
if ((ctx_num_left == 0) || (num < 2)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (num > 1 && ctx_num_left != 0) {
|
||||
c = *ctx_ptr++;
|
||||
ctx_num_left--;
|
||||
*strp++ = c;
|
||||
if (c == '\n') {
|
||||
break;
|
||||
}
|
||||
num--;
|
||||
}
|
||||
|
||||
*strp = '\0';
|
||||
ctx->ptr = ctx_ptr;
|
||||
ctx->num_left = ctx_num_left;
|
||||
return str;
|
||||
}
|
||||
|
||||
/* See documentation in header file. */
|
||||
int iwini_parse_string(const char *string, iwini_handler handler, void *user) {
|
||||
ini_parse_string_ctx ctx;
|
||||
|
||||
ctx.ptr = string;
|
||||
ctx.num_left = strlen(string);
|
||||
return iwini_parse_stream((iwini_reader) ini_reader_string, &ctx, handler,
|
||||
user);
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
#pragma once
|
||||
|
||||
/* inih -- simple .INI file parser
|
||||
|
||||
SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
Copyright (C) 2009-2020, Ben Hoyt
|
||||
|
||||
ini is released under the New BSD license.
|
||||
|
||||
https://github.com/benhoyt/inih
|
||||
|
||||
*/
|
||||
|
||||
#ifndef IWINI_H
|
||||
#define IWINI_H
|
||||
|
||||
#include "basedefs.h"
|
||||
#include "iwlog.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <strings.h>
|
||||
|
||||
IW_EXTERN_C_START
|
||||
|
||||
#define IWINI_PARSE_BOOL(var__) { \
|
||||
if (!strcasecmp(value, "true") || !strcasecmp(value, "on") || !strcasecmp(value, "yes")) { \
|
||||
var__ = true; \
|
||||
} else if (!strcasecmp(value, "false") || !strcasecmp(value, "off") || !strcasecmp(value, "no")) { \
|
||||
var__ = false; \
|
||||
} else { \
|
||||
iwlog_error("Config: Wrong [%s] section property %s value", section, name); \
|
||||
} \
|
||||
}
|
||||
|
||||
/* Nonzero if ini_handler callback should accept lineno parameter. */
|
||||
#ifndef IWINI_HANDLER_LINENO
|
||||
#define IWINI_HANDLER_LINENO 0
|
||||
#endif
|
||||
|
||||
/* Typedef for prototype of handler function. */
|
||||
#if IWINI_HANDLER_LINENO
|
||||
typedef int (*iwini_handler)(
|
||||
void*user, const char*section,
|
||||
const char*name, const char*value,
|
||||
int lineno);
|
||||
#else
|
||||
typedef int (*iwini_handler)(
|
||||
void*user, const char*section,
|
||||
const char*name, const char*value);
|
||||
#endif
|
||||
|
||||
/* Typedef for prototype of fgets-style reader function. */
|
||||
typedef char* (*iwini_reader)(char*str, int num, void*stream);
|
||||
|
||||
/* Parse given INI-style file. May have [section]s, name=value pairs
|
||||
(whitespace stripped), and comments starting with ';' (semicolon). Section
|
||||
is "" if name=value pair parsed before any section heading. name:value
|
||||
pairs are also supported as a concession to Python's configparser.
|
||||
|
||||
For each name=value pair parsed, call handler function with given user
|
||||
pointer as well as section, name, and value (data only valid for duration
|
||||
of handler call). Handler should return nonzero on success, zero on error.
|
||||
|
||||
Returns 0 on success, line number of first error on parse error (doesn't
|
||||
stop on first error), -1 on file open error, or -2 on memory allocation
|
||||
error (only when INI_USE_STACK is zero).
|
||||
*/
|
||||
IW_EXPORT int iwini_parse(const char*filename, iwini_handler handler, void*user);
|
||||
|
||||
/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
|
||||
close the file when it's finished -- the caller must do that. */
|
||||
IW_EXPORT int iwini_parse_file(FILE*file, iwini_handler handler, void*user);
|
||||
|
||||
/* Same as ini_parse(), but takes an ini_reader function pointer instead of
|
||||
filename. Used for implementing custom or string-based I/O (see also
|
||||
iwini_parse_string). */
|
||||
IW_EXPORT int iwini_parse_stream(
|
||||
iwini_reader reader, void*stream, iwini_handler handler,
|
||||
void*user);
|
||||
|
||||
/* Same as ini_parse(), but takes a zero-terminated string with the INI data
|
||||
instead of a file. Useful for parsing INI data from a network socket or
|
||||
already in memory. */
|
||||
IW_EXPORT int iwini_parse_string(const char*string, iwini_handler handler, void*user);
|
||||
|
||||
/* Nonzero to allow multi-line value parsing, in the style of Python's
|
||||
configparser. If allowed, ini_parse() will call the handler with the same
|
||||
name for each subsequent line parsed. */
|
||||
#ifndef IWINI_ALLOW_MULTILINE
|
||||
#define IWINI_ALLOW_MULTILINE 1
|
||||
#endif
|
||||
|
||||
/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
|
||||
the file. See https://github.com/benhoyt/inih/issues/21 */
|
||||
#ifndef IWINI_ALLOW_BOM
|
||||
#define IWINI_ALLOW_BOM 1
|
||||
#endif
|
||||
|
||||
/* Chars that begin a start-of-line comment. Per Python configparser, allow
|
||||
both ; and # comments at the start of a line by default. */
|
||||
#ifndef IWINI_START_COMMENT_PREFIXES
|
||||
#define IWINI_START_COMMENT_PREFIXES ";#"
|
||||
#endif
|
||||
|
||||
/* Nonzero to allow inline comments (with valid inline comment characters
|
||||
specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
|
||||
Python 3.2+ configparser behaviour. */
|
||||
#ifndef IWINI_ALLOW_INLINE_COMMENTS
|
||||
#define IWINI_ALLOW_INLINE_COMMENTS 1
|
||||
#endif
|
||||
#ifndef IWINI_INLINE_COMMENT_PREFIXES
|
||||
#define IWINI_INLINE_COMMENT_PREFIXES ";"
|
||||
#endif
|
||||
|
||||
/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */
|
||||
#ifndef IWINI_USE_STACK
|
||||
#define IWINI_USE_STACK 1
|
||||
#endif
|
||||
|
||||
/* Maximum line length for any line in INI file (stack or heap). Note that
|
||||
this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */
|
||||
#ifndef IWINI_MAX_LINE
|
||||
#define IWINI_MAX_LINE 200
|
||||
#endif
|
||||
|
||||
/* Nonzero to allow heap line buffer to grow via realloc(), zero for a
|
||||
fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is
|
||||
zero. */
|
||||
#ifndef IWINI_ALLOW_REALLOC
|
||||
#define IWINI_ALLOW_REALLOC 0
|
||||
#endif
|
||||
|
||||
/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK
|
||||
is zero. */
|
||||
#ifndef IWINI_INITIAL_ALLOC
|
||||
#define IWINI_INITIAL_ALLOC 200
|
||||
#endif
|
||||
|
||||
/* Stop parsing on first error (default is to keep parsing). */
|
||||
#ifndef IWINI_STOP_ON_FIRST_ERROR
|
||||
#define IWINI_STOP_ON_FIRST_ERROR 0
|
||||
#endif
|
||||
|
||||
/* Nonzero to call the handler at the start of each new section (with
|
||||
name and value NULL). Default is to only call the handler on
|
||||
each name=value pair. */
|
||||
#ifndef IWINI_CALL_HANDLER_ON_NEW_SECTION
|
||||
#define IWINI_CALL_HANDLER_ON_NEW_SECTION 0
|
||||
#endif
|
||||
|
||||
/* Nonzero to allow a name without a value (no '=' or ':' on the line) and
|
||||
call the handler with value NULL in this case. Default is to treat
|
||||
no-value lines as an error. */
|
||||
#ifndef IWINI_ALLOW_NO_VALUE
|
||||
#define IWINI_ALLOW_NO_VALUE 0
|
||||
#endif
|
||||
|
||||
/* Nonzero to use custom ini_malloc, ini_free, and ini_realloc memory
|
||||
allocation functions (INI_USE_STACK must also be 0). These functions must
|
||||
have the same signatures as malloc/free/realloc and behave in a similar
|
||||
way. ini_realloc is only needed if INI_ALLOW_REALLOC is set. */
|
||||
#ifndef IWINI_CUSTOM_ALLOCATOR
|
||||
#define IWINI_CUSTOM_ALLOCATOR 0
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
IW_EXTERN_C_END
|
||||
#endif /* IWINI_H */
|
||||
+8
-8
@@ -166,12 +166,12 @@ char* iwpool_printf(IWPOOL *pool, const char *format, ...) {
|
||||
return res;
|
||||
}
|
||||
|
||||
char** iwpool_split_string(
|
||||
const char** iwpool_split_string(
|
||||
IWPOOL *pool, const char *haystack, const char *split_chars,
|
||||
bool ignore_whitespace) {
|
||||
|
||||
bool ignore_whitespace
|
||||
) {
|
||||
size_t hsz = strlen(haystack);
|
||||
char **ret = iwpool_alloc((hsz + 1) * sizeof(char*), pool);
|
||||
const char **ret = iwpool_alloc((hsz + 1) * sizeof(char*), pool);
|
||||
if (!ret) {
|
||||
return 0;
|
||||
}
|
||||
@@ -206,11 +206,11 @@ char** iwpool_split_string(
|
||||
return ret;
|
||||
}
|
||||
|
||||
char** iwpool_printf_split(
|
||||
const char** iwpool_printf_split(
|
||||
IWPOOL *pool,
|
||||
const char *split_chars, bool ignore_whitespace,
|
||||
const char *format, ...) {
|
||||
|
||||
const char *format, ...
|
||||
) {
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
int size = _iwpool_printf_estimate_size(format, ap);
|
||||
@@ -222,7 +222,7 @@ char** iwpool_printf_split(
|
||||
va_start(ap, format);
|
||||
vsnprintf(buf, size, format, ap);
|
||||
va_end(ap);
|
||||
char **ret = iwpool_split_string(pool, buf, split_chars, ignore_whitespace);
|
||||
const char **ret = iwpool_split_string(pool, buf, split_chars, ignore_whitespace);
|
||||
free(buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
+2
-2
@@ -109,11 +109,11 @@ IW_EXPORT char* iwpool_strndup2(IWPOOL *pool, const char *str, size_t len);
|
||||
*/
|
||||
IW_EXPORT char* iwpool_printf(IWPOOL *pool, const char *format, ...) __attribute__((format(__printf__, 2, 3)));
|
||||
|
||||
IW_EXPORT char** iwpool_split_string(
|
||||
IW_EXPORT const char** iwpool_split_string(
|
||||
IWPOOL *pool, const char *haystack,
|
||||
const char *split_chars, bool ignore_whitespace);
|
||||
|
||||
IW_EXPORT char** iwpool_printf_split(
|
||||
IW_EXPORT const char** iwpool_printf_split(
|
||||
IWPOOL *pool,
|
||||
const char *split_chars, bool ignore_whitespace,
|
||||
const char *format, ...) __attribute__((format(__printf__, 4, 5)));
|
||||
|
||||
@@ -1,994 +0,0 @@
|
||||
// -V::506
|
||||
|
||||
/* Copyright (c) 2014 by Ian Piumarta
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, provided that the above copyright notice(s) and this
|
||||
* permission notice appear in all copies of the Software. Acknowledgement
|
||||
* of the use of this Software in supporting documentation would be
|
||||
* appreciated but is not required.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK.
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <setjmp.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "iwre.h"
|
||||
|
||||
#ifndef RE_ERROR
|
||||
# define RE_ERROR(RE, CODE, MESSAGE) { (RE)->error_message = (MESSAGE); \
|
||||
longjmp(*(RE)->error_env, (re->error_code = RE_ERROR_ ## CODE)); }
|
||||
#endif
|
||||
|
||||
#ifndef RE_MALLOC
|
||||
# define RE_MALLOC(RE, SIZE) re__malloc((RE), (SIZE))
|
||||
|
||||
static void* re__malloc(struct re *re, size_t size) {
|
||||
void *p = malloc(size);
|
||||
if (!p) {
|
||||
RE_ERROR(re, NOMEM, "out of memory");
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef RE_CALLOC
|
||||
# define RE_CALLOC(RE, NMEMB, SIZE) re__calloc((RE), (NMEMB), (SIZE))
|
||||
|
||||
static void* re__calloc(struct re *re, size_t nmemb, size_t size) {
|
||||
void *p = calloc(nmemb, size);
|
||||
if (p) {
|
||||
return p;
|
||||
}
|
||||
if (re) {
|
||||
RE_ERROR(re, NOMEM, "out of memory");
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef RE_REALLOC
|
||||
# define RE_REALLOC(RE, PTR, SIZE) re__realloc((RE), (PTR), (SIZE))
|
||||
|
||||
static inline void* re__realloc(struct re *re, void *ptr, size_t size) {
|
||||
void *p = realloc(ptr, size);
|
||||
if (!p) {
|
||||
RE_ERROR(re, NOMEM, "out of memory");
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef RE_FREE
|
||||
# define RE_FREE(RE, PTR) free(PTR)
|
||||
#endif
|
||||
|
||||
/* arrays */
|
||||
|
||||
#define re_array_of(TYPE) \
|
||||
struct { \
|
||||
int size; \
|
||||
int capacity; \
|
||||
TYPE *at; \
|
||||
}
|
||||
|
||||
#define RE_ARRAY_OF_INITIALISER { 0, 0, 0 }
|
||||
|
||||
#define re_array_append(RE, ARRAY, ELEMENT) \
|
||||
((ARRAY).size++, \
|
||||
(((ARRAY).size > (ARRAY).capacity) \
|
||||
? ((ARRAY).at = RE_REALLOC((RE), (ARRAY).at, \
|
||||
sizeof(*(ARRAY).at) * ((ARRAY).capacity = ((ARRAY).capacity \
|
||||
? ((ARRAY).capacity * 2) \
|
||||
: 8)))) \
|
||||
: (ARRAY).at) \
|
||||
[(ARRAY).size - 1] = (ELEMENT))
|
||||
|
||||
#define re_array_copy(RE, ARRAY) \
|
||||
{ (ARRAY).size, \
|
||||
(ARRAY).size, \
|
||||
memcpy(RE_MALLOC((RE), sizeof((ARRAY).at[0]) * (ARRAY).size), \
|
||||
(ARRAY).at, \
|
||||
sizeof((ARRAY).at[0]) * (ARRAY).size) }
|
||||
|
||||
#define re_array_release(RE, ARRAY) { \
|
||||
if ((ARRAY).at) { \
|
||||
RE_FREE((RE), (ARRAY).at); \
|
||||
(ARRAY).at = 0; \
|
||||
} \
|
||||
}
|
||||
|
||||
/* bit sets */
|
||||
|
||||
struct RE_BitSet;
|
||||
typedef struct RE_BitSet RE_BitSet;
|
||||
|
||||
struct RE_BitSet {
|
||||
int inverted;
|
||||
unsigned char bits[256 / sizeof(unsigned char)];
|
||||
};
|
||||
|
||||
static int re_bitset__includes(RE_BitSet *c, int i) {
|
||||
if ((i < 0) || (255 < i)) {
|
||||
return 0;
|
||||
}
|
||||
return (c->bits[i / 8] >> (i % 8)) & 1;
|
||||
}
|
||||
|
||||
static int re_bitset_includes(RE_BitSet *c, int i) {
|
||||
int inc = re_bitset__includes(c, i);
|
||||
if (c->inverted) {
|
||||
inc = !inc;
|
||||
}
|
||||
return inc;
|
||||
}
|
||||
|
||||
static void re_bitset_add(RE_BitSet *c, int i) {
|
||||
if ((i < 0) || (255 < i)) {
|
||||
return;
|
||||
}
|
||||
c->bits[i / 8] |= (1 << (i % 8));
|
||||
}
|
||||
|
||||
/* character classes */
|
||||
|
||||
static int re_make_char(struct re *re) {
|
||||
const char *p = re->position;
|
||||
if (!*p) {
|
||||
return 0;
|
||||
}
|
||||
int c = *p++;
|
||||
if (('\\' == c) && *p) {
|
||||
c = *p++;
|
||||
}
|
||||
re->position = p;
|
||||
return c;
|
||||
}
|
||||
|
||||
static RE_BitSet* re_make_class(struct re *re) {
|
||||
RE_BitSet *c = RE_CALLOC(re, 1, sizeof(RE_BitSet));
|
||||
int last = -1;
|
||||
c->inverted = ('^' == *re->position); // -V522
|
||||
if (c->inverted) {
|
||||
re->position++;
|
||||
}
|
||||
while (*re->position && (']' != *re->position)) {
|
||||
int this = re->position[0];
|
||||
if (('-' == this) && (last >= 0) && re->position[1] && (']' != re->position[1])) {
|
||||
re->position++;
|
||||
this = re_make_char(re);
|
||||
do {
|
||||
re_bitset_add(c, last++);
|
||||
} while (last <= this);
|
||||
last = -1;
|
||||
} else {
|
||||
this = re_make_char(re);
|
||||
re_bitset_add(c, this);
|
||||
last = this;
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
/* instructions */
|
||||
|
||||
enum { RE_Any, RE_Char, RE_Class, RE_Accept, RE_Jump, RE_Fork, RE_Begin, RE_End, };
|
||||
|
||||
struct RE_Insn;
|
||||
typedef struct RE_Insn RE_Insn;
|
||||
|
||||
struct RE_Insn {
|
||||
int opcode;
|
||||
long x;
|
||||
union {
|
||||
long y;
|
||||
RE_BitSet *c;
|
||||
};
|
||||
union {
|
||||
RE_Insn *next;
|
||||
const char *stamp;
|
||||
};
|
||||
};
|
||||
|
||||
struct RE_Compiled;
|
||||
typedef struct RE_Compiled RE_Compiled;
|
||||
|
||||
/*
|
||||
struct RE_Compiled
|
||||
{
|
||||
int size;
|
||||
RE_Insn *first;
|
||||
RE_Insn *last;
|
||||
};
|
||||
|
||||
#define RE_COMPILED_INITIALISER { 0, 0, 0 }
|
||||
*/
|
||||
|
||||
static RE_Compiled re_insn_new(struct re *re, int opc) {
|
||||
RE_Insn *insn = RE_CALLOC(re, 1, sizeof(RE_Insn));
|
||||
insn->opcode = opc; // -V522
|
||||
RE_Compiled insns = { 1, insn, insn };
|
||||
return insns;
|
||||
}
|
||||
|
||||
static RE_Compiled re_new_Any(struct re *re) {
|
||||
RE_Compiled insns = re_insn_new(re, RE_Any);
|
||||
return insns;
|
||||
}
|
||||
|
||||
static RE_Compiled re_new_Char(struct re *re, int c) {
|
||||
RE_Compiled insns = re_insn_new(re, RE_Char);
|
||||
insns.first->x = c;
|
||||
return insns;
|
||||
}
|
||||
|
||||
static RE_Compiled re_new_Class(struct re *re, RE_BitSet *c) {
|
||||
RE_Compiled insns = re_insn_new(re, RE_Class);
|
||||
insns.first->c = c;
|
||||
return insns;
|
||||
}
|
||||
|
||||
static RE_Compiled re_new_Accept(struct re *re) {
|
||||
RE_Compiled insns = re_insn_new(re, RE_Accept);
|
||||
return insns;
|
||||
}
|
||||
|
||||
static RE_Compiled re_new_Jump(struct re *re, int x) {
|
||||
RE_Compiled insns = re_insn_new(re, RE_Jump);
|
||||
insns.first->x = x;
|
||||
return insns;
|
||||
}
|
||||
|
||||
static RE_Compiled re_new_Fork(struct re *re, int x, int y) {
|
||||
RE_Compiled insns = re_insn_new(re, RE_Fork);
|
||||
insns.first->x = x;
|
||||
insns.first->y = y;
|
||||
return insns;
|
||||
}
|
||||
|
||||
static RE_Compiled re_new_Begin(struct re *re) {
|
||||
RE_Compiled insns = re_insn_new(re, RE_Begin);
|
||||
return insns;
|
||||
}
|
||||
|
||||
static RE_Compiled re_new_End(struct re *re) {
|
||||
RE_Compiled insns = re_insn_new(re, RE_End);
|
||||
return insns;
|
||||
}
|
||||
|
||||
static void re_program_append(RE_Compiled *insns, RE_Compiled tail) {
|
||||
insns->last->next = tail.first;
|
||||
insns->last = tail.last;
|
||||
insns->size += tail.size;
|
||||
}
|
||||
|
||||
static void re_program_prepend(RE_Compiled *insns, RE_Compiled head) {
|
||||
head.last->next = insns->first;
|
||||
insns->first = head.first;
|
||||
insns->size += head.size;
|
||||
}
|
||||
|
||||
static void re_program_free(struct re *re, RE_Compiled *insns) {
|
||||
int i;
|
||||
for (i = 0; i < insns->size; ++i) {
|
||||
switch (insns->first[i].opcode) {
|
||||
case RE_Class: {
|
||||
RE_FREE(re, insns->first[i].c);
|
||||
insns->first[i].c = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
RE_FREE(re, insns->first);
|
||||
insns->first = insns->last = 0;
|
||||
insns->size = 0;
|
||||
}
|
||||
|
||||
/* compilation */
|
||||
|
||||
/*
|
||||
struct re
|
||||
{
|
||||
char *expression;
|
||||
char *position;
|
||||
jmp_buf *error_env;
|
||||
int error_code;
|
||||
char *error_message;
|
||||
struct RE_Compiled code;
|
||||
char **matches;
|
||||
int nmatches;
|
||||
};
|
||||
*/
|
||||
|
||||
static RE_Compiled re_compile_expression(struct re *re);
|
||||
|
||||
static RE_Compiled re_compile_primary(struct re *re) {
|
||||
int c = *re->position++;
|
||||
assert(0 != c);
|
||||
switch (c) {
|
||||
case '\\': {
|
||||
if (*re->position) {
|
||||
c = *re->position++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '.': {
|
||||
return re_new_Any(re);
|
||||
}
|
||||
case '[': {
|
||||
RE_BitSet *cc = re_make_class(re);
|
||||
if (']' != *re->position) {
|
||||
RE_FREE(re, cc);
|
||||
RE_ERROR(re, CHARSET, "expected ']' at end of character set");
|
||||
}
|
||||
re->position++;
|
||||
return re_new_Class(re, cc);
|
||||
};
|
||||
case '(': {
|
||||
RE_Compiled insns = re_compile_expression(re);
|
||||
if (')' != *re->position) {
|
||||
RE_Insn *insn, *next;
|
||||
for (insn = insns.first; insn; insn = next) {
|
||||
next = insn->next;
|
||||
RE_FREE(re, insn);
|
||||
}
|
||||
RE_ERROR(re, SUBEXP, "expected ')' at end of subexpression");
|
||||
}
|
||||
re->position++;
|
||||
return insns;
|
||||
}
|
||||
case '{': {
|
||||
RE_Compiled insns = re_compile_expression(re);
|
||||
if ('}' != *re->position) {
|
||||
RE_Insn *insn, *next;
|
||||
for (insn = insns.first; insn; insn = next) {
|
||||
next = insn->next;
|
||||
RE_FREE(re, insn);
|
||||
}
|
||||
RE_ERROR(re, SUBMATCH, "expected '}' at end of submatch");
|
||||
}
|
||||
re_program_prepend(&insns, re_new_Begin(re));
|
||||
re_program_append(&insns, re_new_End(re));
|
||||
re->position++;
|
||||
return insns;
|
||||
}
|
||||
}
|
||||
return re_new_Char(re, c);
|
||||
}
|
||||
|
||||
static RE_Compiled re_compile_suffix(struct re *re) {
|
||||
RE_Compiled insns = re_compile_primary(re);
|
||||
switch (*re->position) {
|
||||
case '?': {
|
||||
re->position++;
|
||||
if ('?' == *re->position) {
|
||||
re->position++;
|
||||
re_program_prepend(&insns, re_new_Fork(re, insns.size, 0));
|
||||
} else {
|
||||
re_program_prepend(&insns, re_new_Fork(re, 0, insns.size));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '*': {
|
||||
re->position++;
|
||||
if ('?' == *re->position) {
|
||||
re->position++;
|
||||
re_program_prepend(&insns, re_new_Fork(re, insns.size + 1, 0));
|
||||
} else {
|
||||
re_program_prepend(&insns, re_new_Fork(re, 0, insns.size + 1));
|
||||
}
|
||||
re_program_append(&insns, re_new_Jump(re, -(insns.size + 1)));
|
||||
break;
|
||||
}
|
||||
case '+': {
|
||||
re->position++;
|
||||
if ('?' == *re->position) {
|
||||
re->position++;
|
||||
re_program_append(&insns, re_new_Fork(re, 0, -(insns.size + 1)));
|
||||
} else {
|
||||
re_program_append(&insns, re_new_Fork(re, -(insns.size + 1), 0));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return insns;
|
||||
}
|
||||
|
||||
static RE_Compiled re_compile_sequence(struct re *re) {
|
||||
if (!*re->position) {
|
||||
return re_new_Accept(re);
|
||||
}
|
||||
RE_Compiled head = re_compile_suffix(re);
|
||||
while (*re->position && !strchr("|)}>", *re->position)) {
|
||||
re_program_append(&head, re_compile_suffix(re));
|
||||
}
|
||||
if (!*re->position) {
|
||||
re_program_append(&head, re_new_Accept(re));
|
||||
}
|
||||
return head;
|
||||
}
|
||||
|
||||
static RE_Compiled re_compile_expression(struct re *re) {
|
||||
RE_Compiled head = re_compile_sequence(re);
|
||||
while ('|' == *re->position) {
|
||||
re->position++;
|
||||
RE_Compiled tail = re_compile_sequence(re);
|
||||
re_program_append(&head, re_new_Jump(re, tail.size));
|
||||
re_program_prepend(&head, re_new_Fork(re, 0, head.size));
|
||||
re_program_append(&head, tail);
|
||||
}
|
||||
return head;
|
||||
}
|
||||
|
||||
static RE_Compiled re_compile(struct re *re) {
|
||||
jmp_buf env;
|
||||
RE_Compiled insns = RE_COMPILED_INITIALISER;
|
||||
re->error_env = &env;
|
||||
if (setjmp(env)) { /* syntax error */
|
||||
return insns;
|
||||
}
|
||||
insns = re_compile_expression(re);
|
||||
re_array_of(RE_Insn) program = RE_ARRAY_OF_INITIALISER;
|
||||
RE_Insn *insn, *next;
|
||||
for (insn = insns.first; insn; insn = next) {
|
||||
re_array_append(re, program, *insn);
|
||||
next = insn->next;
|
||||
RE_FREE(re, insn);
|
||||
}
|
||||
|
||||
#if 0
|
||||
int i;
|
||||
for (i = 0; i < program.size; ++i) {
|
||||
RE_Insn *insn = &program.at[i];
|
||||
printf("%03i ", i);
|
||||
switch (insn->opcode) {
|
||||
case RE_Any:
|
||||
printf("Any\n");
|
||||
break;
|
||||
case RE_Char:
|
||||
printf("Char %li\n", insn->x);
|
||||
break;
|
||||
case RE_Class: {
|
||||
printf("Class ");
|
||||
{
|
||||
RE_BitSet *c = insn->c;
|
||||
int i;
|
||||
putchar('[');
|
||||
if (c->inverted) {
|
||||
putchar('^');
|
||||
}
|
||||
for (i = 0; i < 256; ++i)
|
||||
if (re_bitset__includes(c, i)) {
|
||||
switch (i) {
|
||||
case '\a':
|
||||
printf("\\a");
|
||||
break;
|
||||
case '\b':
|
||||
printf("\\b");
|
||||
break;
|
||||
case '\t':
|
||||
printf("\\t");
|
||||
break;
|
||||
case '\n':
|
||||
printf("\\n");
|
||||
break;
|
||||
case '\v':
|
||||
printf("\\v");
|
||||
break;
|
||||
case '\f':
|
||||
printf("\\f");
|
||||
break;
|
||||
case '\r':
|
||||
printf("\\r");
|
||||
break;
|
||||
case '\\':
|
||||
printf("\\\\");
|
||||
break;
|
||||
case ']':
|
||||
printf("\\]");
|
||||
break;
|
||||
case '^':
|
||||
printf("\\^");
|
||||
break;
|
||||
case '-':
|
||||
printf("\\-");
|
||||
break;
|
||||
default:
|
||||
if (isprint(i)) {
|
||||
putchar(i);
|
||||
} else {
|
||||
printf("\\x%02x", i);
|
||||
}
|
||||
}
|
||||
}
|
||||
putchar(']');
|
||||
}
|
||||
putchar('\n');
|
||||
break;
|
||||
}
|
||||
case RE_Accept:
|
||||
printf("Accept\n");
|
||||
break;
|
||||
case RE_Jump:
|
||||
printf("Jump %li -> %03li\n", insn->x, i + 1 + insn->x);
|
||||
break;
|
||||
case RE_Fork:
|
||||
printf("Fork %li %li -> %03li %03li\n", insn->x, insn->y, i + 1 + insn->x, i + 1 + insn->y);
|
||||
break;
|
||||
case RE_Begin:
|
||||
printf("Begin\n");
|
||||
break;
|
||||
case RE_End:
|
||||
printf("End\n");
|
||||
break;
|
||||
default:
|
||||
printf("?%i\n", insn->opcode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
assert(program.size == insns.size);
|
||||
|
||||
insns.first = program.at;
|
||||
insns.last = insns.first + insns.size;
|
||||
|
||||
return insns;
|
||||
}
|
||||
|
||||
/* submatch recording */
|
||||
|
||||
typedef re_array_of(const char*) re_array_of_charp;
|
||||
|
||||
struct RE_Submatches;
|
||||
typedef struct RE_Submatches RE_Submatches;
|
||||
|
||||
struct RE_Submatches {
|
||||
int refs;
|
||||
re_array_of_charp beginnings;
|
||||
re_array_of_charp endings;
|
||||
};
|
||||
|
||||
static RE_Submatches* re_submatches_copy(struct re *re, RE_Submatches *orig) {
|
||||
RE_Submatches *subs = RE_CALLOC(re, 1, sizeof(RE_Submatches));
|
||||
if (orig) {
|
||||
subs->beginnings = (re_array_of_charp) re_array_copy(re, orig->beginnings); // -V522
|
||||
subs->endings = (re_array_of_charp) re_array_copy(re, orig->endings);
|
||||
}
|
||||
return subs;
|
||||
}
|
||||
|
||||
static void re_submatches_free(struct re *re, RE_Submatches *subs) {
|
||||
assert(subs);
|
||||
assert(!subs->refs);
|
||||
re_array_release(re, subs->beginnings);
|
||||
re_array_release(re, subs->endings);
|
||||
RE_FREE(re, subs);
|
||||
}
|
||||
|
||||
static inline RE_Submatches* re_submatches_link(RE_Submatches *subs) {
|
||||
if (subs) {
|
||||
subs->refs++;
|
||||
}
|
||||
return subs;
|
||||
}
|
||||
|
||||
static inline void re_submatches_unlink(struct re *re, RE_Submatches *subs) {
|
||||
if (subs && (0 == --(subs->refs))) {
|
||||
re_submatches_free(re, subs);
|
||||
}
|
||||
}
|
||||
|
||||
/* matching */
|
||||
|
||||
struct RE_Thread;
|
||||
typedef struct RE_Thread RE_Thread;
|
||||
|
||||
struct RE_Thread {
|
||||
RE_Insn *pc;
|
||||
RE_Submatches *submatches;
|
||||
};
|
||||
|
||||
struct RE_ThreadList;
|
||||
typedef struct RE_ThreadList RE_ThreadList;
|
||||
|
||||
struct RE_ThreadList {
|
||||
int size;
|
||||
RE_Thread *at;
|
||||
};
|
||||
|
||||
static inline RE_Thread re_thread(RE_Insn *pc, RE_Submatches *subs) {
|
||||
return (RE_Thread) {
|
||||
pc, subs
|
||||
};
|
||||
}
|
||||
|
||||
static void re_thread_schedule(
|
||||
struct re *re, RE_ThreadList *threads, RE_Insn *pc, const char *sp,
|
||||
RE_Submatches *subs
|
||||
) {
|
||||
if (pc->stamp == sp) {
|
||||
return;
|
||||
}
|
||||
pc->stamp = sp;
|
||||
|
||||
switch (pc->opcode) {
|
||||
case RE_Jump:
|
||||
re_thread_schedule(re, threads, pc + 1 + pc->x, sp, subs);
|
||||
return;
|
||||
case RE_Fork:
|
||||
re_thread_schedule(re, threads, pc + 1 + pc->x, sp, subs);
|
||||
re_thread_schedule(re, threads, pc + 1 + pc->y, sp, subs);
|
||||
return;
|
||||
case RE_Begin:
|
||||
subs = re_submatches_copy(re, subs);
|
||||
re_array_append(re, subs->beginnings, sp); // -V522
|
||||
re_thread_schedule(re, threads, pc + 1, sp, subs);
|
||||
if (!subs->refs) {
|
||||
re_submatches_free(re, subs);
|
||||
}
|
||||
return;
|
||||
case RE_End: {
|
||||
subs = re_submatches_copy(re, subs);
|
||||
# if 0 /* non-nesting groups: ab{cd{ef}gh}ij => {cdef} {efgh} */
|
||||
re_array_append(re, subs->endings, sp);
|
||||
# else /* nesting groups: ab{cd{ef}gh}ij => {cdefgh} {ef} */
|
||||
while (subs->endings.size < subs->beginnings.size) re_array_append(re, subs->endings, 0);
|
||||
int i;
|
||||
for (i = subs->endings.size; i--; ) {
|
||||
if (!subs->endings.at[i]) {
|
||||
subs->endings.at[i] = sp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
# endif
|
||||
re_thread_schedule(re, threads, pc + 1, sp, subs);
|
||||
if (!subs->refs) {
|
||||
re_submatches_free(re, subs);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
threads->at[threads->size++] = re_thread(pc, re_submatches_link(subs));
|
||||
}
|
||||
|
||||
static int re_program_run(struct re *re, const char *input, char const ***saved, int *nsaved) {
|
||||
int matched = RE_ERROR_NOMATCH;
|
||||
if (!re) {
|
||||
return matched;
|
||||
}
|
||||
|
||||
RE_Submatches *submatches = 0;
|
||||
RE_ThreadList a = { 0, 0 }, b = { 0, 0 }, *here = &a, *next = &b;
|
||||
|
||||
const char *sp = input;
|
||||
re->position = 0;
|
||||
|
||||
jmp_buf env;
|
||||
re->error_env = &env;
|
||||
|
||||
if (setjmp(env)) { /* out of memory */
|
||||
matched = re->error_code;
|
||||
goto bailout;
|
||||
}
|
||||
|
||||
a.at = RE_CALLOC(re, re->code.size, sizeof(RE_Thread));
|
||||
b.at = RE_CALLOC(re, re->code.size, sizeof(RE_Thread));
|
||||
|
||||
re_thread_schedule(re, here, re->code.first, input, 0);
|
||||
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < re->code.size; ++i) {
|
||||
re->code.first[i].stamp = 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (sp = input; here->size; ++sp) {
|
||||
int i;
|
||||
for (i = 0; i < here->size; ++i) {
|
||||
RE_Thread t = here->at[i];
|
||||
switch (t.pc->opcode) {
|
||||
case RE_Any: {
|
||||
if (*sp) {
|
||||
re_thread_schedule(re, next, t.pc + 1, sp + 1, t.submatches);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case RE_Char: {
|
||||
if (*sp == t.pc->x) {
|
||||
re_thread_schedule(re, next, t.pc + 1, sp + 1, t.submatches);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case RE_Class: {
|
||||
if (re_bitset_includes(t.pc->c, *sp)) {
|
||||
re_thread_schedule(re, next, t.pc + 1, sp + 1, t.submatches);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case RE_Accept: {
|
||||
matched = sp - input;
|
||||
re_submatches_unlink(re, submatches);
|
||||
submatches = re_submatches_link(t.submatches);
|
||||
while (i < here->size) re_submatches_unlink(re, here->at[i++].submatches);
|
||||
goto nextchar;
|
||||
}
|
||||
default:
|
||||
RE_ERROR(re, ENGINE, "illegal instruction in compiled regular expression (please report this bug)");
|
||||
}
|
||||
re_submatches_unlink(re, t.submatches);
|
||||
}
|
||||
nextchar:
|
||||
;
|
||||
RE_ThreadList *tmp = here;
|
||||
here = next;
|
||||
next = tmp;
|
||||
next->size = 0;
|
||||
if (!*sp) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bailout:
|
||||
re->position = sp;
|
||||
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < here->size; ++i) {
|
||||
re_submatches_unlink(re, here->at[i].submatches);
|
||||
}
|
||||
}
|
||||
|
||||
RE_FREE(re, a.at);
|
||||
RE_FREE(re, b.at);
|
||||
|
||||
if (submatches) {
|
||||
if (saved && nsaved && (matched >= 0)) {
|
||||
assert(submatches->beginnings.size == submatches->endings.size);
|
||||
*nsaved = submatches->beginnings.size * 2;
|
||||
*saved = RE_CALLOC(re, *nsaved, sizeof(char*));
|
||||
int i;
|
||||
for (i = 0; i < *nsaved; i += 2) {
|
||||
(*saved)[i + 0] = submatches->beginnings.at[i / 2];
|
||||
(*saved)[i + 1] = submatches->endings.at[i / 2];
|
||||
}
|
||||
}
|
||||
re_submatches_unlink(re, submatches);
|
||||
}
|
||||
|
||||
return matched;
|
||||
}
|
||||
|
||||
/* public interface */
|
||||
|
||||
struct re* iwre_new(const char *expr) {
|
||||
struct re *re = RE_CALLOC(0, 1, sizeof(struct re));
|
||||
if (re) {
|
||||
re->expression = expr;
|
||||
}
|
||||
return re;
|
||||
}
|
||||
|
||||
int iwre_match(struct re *re, const char *input) {
|
||||
RE_FREE(re, re->matches);
|
||||
re->matches = 0;
|
||||
re->nmatches = 0;
|
||||
if (!re->expression) {
|
||||
return 0;
|
||||
}
|
||||
if (!re->code.size) {
|
||||
re->position = re->expression;
|
||||
re->error_code = 0;
|
||||
re->error_message = 0;
|
||||
re->code = re_compile(re);
|
||||
if (re->error_code) {
|
||||
return re->error_code;
|
||||
}
|
||||
re->position = 0;
|
||||
}
|
||||
return re_program_run(re, input, &re->matches, &re->nmatches);
|
||||
}
|
||||
|
||||
void iwre_release(struct re *re) {
|
||||
RE_FREE(re, re->matches);
|
||||
if (re->code.first) {
|
||||
re_program_free(re, &re->code);
|
||||
}
|
||||
memset(re, 0, sizeof(*re));
|
||||
}
|
||||
|
||||
void iwre_reset(struct re *re, const char *expression) {
|
||||
iwre_release(re);
|
||||
re->expression = expression;
|
||||
}
|
||||
|
||||
void iwre_free(struct re *re) {
|
||||
iwre_release(re);
|
||||
RE_FREE(0, re);
|
||||
}
|
||||
|
||||
/* utility */
|
||||
|
||||
static int re_digit(int c, int base) {
|
||||
if ((c >= '0') && (c <= '9')) {
|
||||
c -= '0';
|
||||
} else if ((c >= 'A') && (c <= 'Z')) {
|
||||
c -= ('A' - 10);
|
||||
} else if ((c >= 'a') && (c <= 'z')) {
|
||||
c -= ('a' - 10);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
if (c >= base) {
|
||||
return -1;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
static int re_byte(char **sp, int least, int most, int base, int liberal) {
|
||||
int c = 0;
|
||||
char *s = *sp;
|
||||
while (s - *sp < most) {
|
||||
int d = re_digit(*s, base);
|
||||
if (d < 0) {
|
||||
break;
|
||||
}
|
||||
++s;
|
||||
c = c * base + d;
|
||||
}
|
||||
if (s - *sp < least) {
|
||||
if (liberal) {
|
||||
return (*sp)[-1];
|
||||
}
|
||||
--*sp;
|
||||
return '\\';
|
||||
}
|
||||
*sp = s;
|
||||
return c;
|
||||
}
|
||||
|
||||
static int re_log2floor(unsigned int n) {
|
||||
if (!n) {
|
||||
return 0;
|
||||
}
|
||||
int b = 1;
|
||||
# define _do(x) if (n >= (1U << x)) (b += x), (n >>= x)
|
||||
_do(16);
|
||||
_do(8);
|
||||
_do(4);
|
||||
_do(2);
|
||||
_do(1);
|
||||
# undef _do
|
||||
return b;
|
||||
}
|
||||
|
||||
static void re_escape_utf8(char **sp, unsigned int c) {
|
||||
char *s = *sp;
|
||||
if (c < 128) {
|
||||
*s++ = c;
|
||||
} else { /* this is good for up to 36 bits of c, which proves that Gordon Bell was right all along */
|
||||
int n = re_log2floor(c) / 6;
|
||||
int m = 6 * n;
|
||||
*s++ = (0xff << (7 - n)) + (c >> m);
|
||||
while ((m -= 6) >= 0) *s++ = 0x80 + ((c >> m) & 0x3F);
|
||||
}
|
||||
*sp = s;
|
||||
}
|
||||
|
||||
char* iwre_escape(char *s, int liberal) {
|
||||
char *in = s, *out = s;
|
||||
int c;
|
||||
while ((c = *in++)) {
|
||||
int u = 0;
|
||||
if ('\\' == c) {
|
||||
c = *in++;
|
||||
switch (c) {
|
||||
case '0':
|
||||
case '1':
|
||||
c = re_byte(&in, 1, 3, 8, liberal);
|
||||
break;
|
||||
case '\\':
|
||||
//c = '\\';
|
||||
break;
|
||||
case 'a':
|
||||
c = '\a';
|
||||
break;
|
||||
case 'b':
|
||||
c = '\b';
|
||||
break;
|
||||
case 'f':
|
||||
c = '\f';
|
||||
break;
|
||||
case 'n':
|
||||
c = '\n';
|
||||
break;
|
||||
case 'r':
|
||||
c = '\r';
|
||||
break;
|
||||
case 't':
|
||||
c = '\t';
|
||||
break;
|
||||
case 'U':
|
||||
c = re_byte(&in, 8, 8, 16, liberal);
|
||||
u = 1;
|
||||
break;
|
||||
case 'u':
|
||||
c = re_byte(&in, 4, 4, 16, liberal);
|
||||
u = 1;
|
||||
break;
|
||||
case 'v':
|
||||
c = '\v';
|
||||
break;
|
||||
case 'x':
|
||||
c = re_byte(&in, 1, 2, 16, liberal);
|
||||
break;
|
||||
default: {
|
||||
if (!liberal) { /* pass escape character through unharmed */
|
||||
--in;
|
||||
c = '\\';
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (u) {
|
||||
re_escape_utf8(&out, c);
|
||||
} else {
|
||||
*out++ = c;
|
||||
}
|
||||
}
|
||||
assert(out <= in);
|
||||
*out = 0;
|
||||
return s;
|
||||
}
|
||||
|
||||
/* testing */
|
||||
|
||||
#ifdef LWRE_TEST
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
/* echo stdin to stout with ANSI terminal escapes to turn every number red */
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
static struct re re = RE_INITIALISER("(.*?{[0-9]+})*");
|
||||
char buf[1024];
|
||||
while (fgets(buf, sizeof(buf), stdin)) {
|
||||
int n;
|
||||
if (((n = lwre_match(&re, buf)) < 0) && (RE_ERROR_NOMATCH != n)) {
|
||||
fprintf(stderr, "%i %ss: %s\n", n, re.error_message, re.position);
|
||||
break;
|
||||
} else {
|
||||
char *p = buf;
|
||||
int n = 0;
|
||||
while (*p) {
|
||||
if ((n < re.nmatches) && (p == re.matches[n])) {
|
||||
if (n & 1) {
|
||||
printf("\033[0m"); /* end of match: clear all attributes */
|
||||
} else {
|
||||
printf("\033[1;31m"); /* start of match: bold and foreground red */
|
||||
}
|
||||
++n;
|
||||
}
|
||||
putchar(*p++);
|
||||
}
|
||||
}
|
||||
}
|
||||
lwre_release(&re);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* REGEXP_TEST */
|
||||
@@ -1,75 +0,0 @@
|
||||
#pragma once
|
||||
#ifndef IWRE_H
|
||||
#define IWRE_H
|
||||
|
||||
/**************************************************************************************************
|
||||
* IOWOW library
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2012-2022 Softmotions Ltd <info@softmotions.com>
|
||||
*
|
||||
* 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.
|
||||
*************************************************************************************************/
|
||||
|
||||
#include <setjmp.h>
|
||||
#include "basedefs.h"
|
||||
|
||||
struct RE_Insn;
|
||||
|
||||
struct RE_Compiled {
|
||||
int size;
|
||||
struct RE_Insn *first;
|
||||
struct RE_Insn *last;
|
||||
};
|
||||
|
||||
#define RE_COMPILED_INITIALISER { 0, 0, 0 }
|
||||
|
||||
struct re {
|
||||
const char *expression;
|
||||
const char *position;
|
||||
jmp_buf *error_env;
|
||||
int error_code;
|
||||
char *error_message;
|
||||
struct RE_Compiled code;
|
||||
const char **matches;
|
||||
int nmatches;
|
||||
#ifdef RE_EXTRA_MEMBERS
|
||||
RE_MEMBERS
|
||||
#endif
|
||||
};
|
||||
|
||||
#define RE_INITIALISER(EXPR) { (EXPR), 0, 0, 0, 0, RE_COMPILED_INITIALISER, 0, 0 }
|
||||
|
||||
#define RE_ERROR_NONE 0
|
||||
#define RE_ERROR_NOMATCH -1
|
||||
#define RE_ERROR_NOMEM -2
|
||||
#define RE_ERROR_CHARSET -3
|
||||
#define RE_ERROR_SUBEXP -4
|
||||
#define RE_ERROR_SUBMATCH -5
|
||||
#define RE_ERROR_ENGINE -6
|
||||
|
||||
IW_EXPORT IW_ALLOC struct re* iwre_new(const char *expression);
|
||||
IW_EXPORT int iwre_match(struct re *re, const char *input);
|
||||
IW_EXPORT void iwre_release(struct re *re);
|
||||
IW_EXPORT void iwre_reset(struct re *re, const char *expression);
|
||||
IW_EXPORT void iwre_free(struct re *re);
|
||||
IW_EXPORT char* iwre_escape(char *string, int liberal);
|
||||
|
||||
#endif /* __lwre_h_ */
|
||||
@@ -1,226 +0,0 @@
|
||||
#include "iwsha2.h"
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define CHUNK_SIZE 64
|
||||
#define TOTAL_LEN_LEN 8
|
||||
|
||||
/*
|
||||
* Comments from pseudo-code at https://en.wikipedia.org/wiki/SHA-2 are reproduced here.
|
||||
* When useful for clarification, portions of the pseudo-code are reproduced here too.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Initialize array of round constants:
|
||||
* (first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311):
|
||||
*/
|
||||
static const uint32_t k[] = {
|
||||
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
||||
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
||||
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
||||
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
||||
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
||||
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
||||
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
||||
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
|
||||
};
|
||||
|
||||
struct buffer_state {
|
||||
const uint8_t *p;
|
||||
size_t len;
|
||||
size_t total_len;
|
||||
int single_one_delivered; /* bool */
|
||||
int total_len_delivered; /* bool */
|
||||
};
|
||||
|
||||
IW_INLINE uint32_t right_rot(uint32_t value, unsigned int count) {
|
||||
/*
|
||||
* Defined behaviour in standard C for all count where 0 < count < 32,
|
||||
* which is what we need here.
|
||||
*/
|
||||
return value >> count | value << (32 - count);
|
||||
}
|
||||
|
||||
static void init_buf_state(struct buffer_state *state, const void *input, size_t len) {
|
||||
state->p = input;
|
||||
state->len = len;
|
||||
state->total_len = len;
|
||||
state->single_one_delivered = 0;
|
||||
state->total_len_delivered = 0;
|
||||
}
|
||||
|
||||
/* Return value: bool */
|
||||
static int calc_chunk(uint8_t chunk[CHUNK_SIZE], struct buffer_state *state) {
|
||||
size_t space_in_chunk;
|
||||
|
||||
if (state->total_len_delivered) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (state->len >= CHUNK_SIZE) {
|
||||
memcpy(chunk, state->p, CHUNK_SIZE);
|
||||
state->p += CHUNK_SIZE;
|
||||
state->len -= CHUNK_SIZE;
|
||||
return 1;
|
||||
}
|
||||
|
||||
memcpy(chunk, state->p, state->len);
|
||||
chunk += state->len;
|
||||
space_in_chunk = CHUNK_SIZE - state->len;
|
||||
state->p += state->len;
|
||||
state->len = 0;
|
||||
|
||||
/* If we are here, space_in_chunk is one at minimum. */
|
||||
if (!state->single_one_delivered) {
|
||||
*chunk++ = 0x80;
|
||||
space_in_chunk -= 1;
|
||||
state->single_one_delivered = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Now:
|
||||
* - either there is enough space left for the total length, and we can conclude,
|
||||
* - or there is too little space left, and we have to pad the rest of this chunk with zeroes.
|
||||
* In the latter case, we will conclude at the next invokation of this function.
|
||||
*/
|
||||
if (space_in_chunk >= TOTAL_LEN_LEN) {
|
||||
const size_t left = space_in_chunk - TOTAL_LEN_LEN;
|
||||
size_t len = state->total_len;
|
||||
int i;
|
||||
memset(chunk, 0x00, left);
|
||||
chunk += left;
|
||||
|
||||
/* Storing of len * 8 as a big endian 64-bit without overflow. */
|
||||
chunk[7] = (uint8_t) (len << 3);
|
||||
len >>= 5;
|
||||
for (i = 6; i >= 0; i--) {
|
||||
chunk[i] = (uint8_t) len;
|
||||
len >>= 8;
|
||||
}
|
||||
state->total_len_delivered = 1;
|
||||
} else {
|
||||
memset(chunk, 0x00, space_in_chunk);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Limitations:
|
||||
* - Since input is a pointer in RAM, the data to hash should be in RAM, which could be a problem
|
||||
* for large data sizes.
|
||||
* - SHA algorithms theoretically operate on bit strings. However, this implementation has no support
|
||||
* for bit string lengths that are not multiples of eight, and it really operates on arrays of bytes.
|
||||
* In particular, the len parameter is a number of bytes.
|
||||
*/
|
||||
void iwsha256(const void *input, size_t len, uint8_t hash_out[32]) {
|
||||
/*
|
||||
* Note 1: All integers (expect indexes) are 32-bit unsigned integers and addition is calculated modulo 2^32.
|
||||
* Note 2: For each round, there is one round constant k[i] and one entry in the message schedule array w[i], 0 = i =
|
||||
* 63
|
||||
* Note 3: The compression function uses 8 working variables, a through h
|
||||
* Note 4: Big-endian convention is used when expressing the constants in this pseudocode,
|
||||
* and when parsing message block data from bytes to words, for example,
|
||||
* the first word of the input message "abc" after padding is 0x61626380
|
||||
*/
|
||||
|
||||
/*
|
||||
* Initialize hash values:
|
||||
* (first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19):
|
||||
*/
|
||||
uint32_t h[] = { 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 };
|
||||
unsigned i, j;
|
||||
|
||||
/* 512-bit chunks is what we will operate on. */
|
||||
uint8_t chunk[64];
|
||||
|
||||
struct buffer_state state;
|
||||
|
||||
init_buf_state(&state, input, len);
|
||||
|
||||
while (calc_chunk(chunk, &state)) {
|
||||
uint32_t ah[8];
|
||||
|
||||
const uint8_t *p = chunk;
|
||||
|
||||
/* Initialize working variables to current hash value: */
|
||||
for (i = 0; i < 8; i++) {
|
||||
ah[i] = h[i];
|
||||
}
|
||||
|
||||
/* Compression function main loop: */
|
||||
for (i = 0; i < 4; i++) {
|
||||
/*
|
||||
* The w-array is really w[64], but since we only need
|
||||
* 16 of them at a time, we save stack by calculating
|
||||
* 16 at a time.
|
||||
*
|
||||
* This optimization was not there initially and the
|
||||
* rest of the comments about w[64] are kept in their
|
||||
* initial state.
|
||||
*/
|
||||
|
||||
/*
|
||||
* create a 64-entry message schedule array w[0..63] of 32-bit words
|
||||
* (The initial values in w[0..63] don't matter, so many implementations zero them here)
|
||||
* copy chunk into first 16 words w[0..15] of the message schedule array
|
||||
*/
|
||||
uint32_t w[16];
|
||||
|
||||
for (j = 0; j < 16; j++) {
|
||||
if (i == 0) {
|
||||
w[j] = (uint32_t) p[0] << 24 | (uint32_t) p[1] << 16
|
||||
| (uint32_t) p[2] << 8 | (uint32_t) p[3];
|
||||
p += 4;
|
||||
} else {
|
||||
/* Extend the first 16 words into the remaining 48 words w[16..63] of the message schedule array: */
|
||||
const uint32_t s0
|
||||
= right_rot(w[(j + 1) & 0xf], 7) ^ right_rot(w[(j + 1) & 0xf], 18) ^ (w[(j + 1) & 0xf] >> 3);
|
||||
const uint32_t s1
|
||||
= right_rot(w[(j + 14) & 0xf], 17) ^ right_rot(w[(j + 14) & 0xf], 19) ^ (w[(j + 14) & 0xf] >> 10);
|
||||
w[j] = w[j] + s0 + w[(j + 9) & 0xf] + s1;
|
||||
}
|
||||
const uint32_t s1 = right_rot(ah[4], 6) ^ right_rot(ah[4], 11) ^ right_rot(ah[4], 25);
|
||||
const uint32_t ch = (ah[4] & ah[5]) ^ (~ah[4] & ah[6]);
|
||||
const uint32_t temp1 = ah[7] + s1 + ch + k[i << 4 | j] + w[j];
|
||||
const uint32_t s0 = right_rot(ah[0], 2) ^ right_rot(ah[0], 13) ^ right_rot(ah[0], 22);
|
||||
const uint32_t maj = (ah[0] & ah[1]) ^ (ah[0] & ah[2]) ^ (ah[1] & ah[2]);
|
||||
const uint32_t temp2 = s0 + maj;
|
||||
|
||||
ah[7] = ah[6];
|
||||
ah[6] = ah[5];
|
||||
ah[5] = ah[4];
|
||||
ah[4] = ah[3] + temp1;
|
||||
ah[3] = ah[2];
|
||||
ah[2] = ah[1];
|
||||
ah[1] = ah[0];
|
||||
ah[0] = temp1 + temp2;
|
||||
}
|
||||
}
|
||||
|
||||
/* Add the compressed chunk to the current hash value: */
|
||||
for (i = 0; i < 8; i++) {
|
||||
h[i] += ah[i];
|
||||
}
|
||||
}
|
||||
|
||||
/* Produce the final hash value (big-endian): */
|
||||
for (i = 0, j = 0; i < 8; i++) {
|
||||
hash_out[j++] = (uint8_t) (h[i] >> 24);
|
||||
hash_out[j++] = (uint8_t) (h[i] >> 16);
|
||||
hash_out[j++] = (uint8_t) (h[i] >> 8);
|
||||
hash_out[j++] = (uint8_t) h[i];
|
||||
}
|
||||
}
|
||||
|
||||
void iwsha256str(const void *input, size_t len, char str_out[65]) {
|
||||
uint8_t hash[32];
|
||||
iwsha256(input, len, hash);
|
||||
iwhash2str(hash, str_out);
|
||||
}
|
||||
|
||||
void iwhash2str(uint8_t hash[32], char str_out[65]) {
|
||||
for (int i = 0; i < 32; i++) {
|
||||
str_out += sprintf(str_out, "%02x", hash[i]);
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
#pragma once
|
||||
#ifndef IWSHA2_H
|
||||
#define IWSHA2_H
|
||||
|
||||
/**************************************************************************************************
|
||||
* SHA-256 hash generator.
|
||||
* Based on https://github.com/amosnier/sha-2
|
||||
*
|
||||
* This is free and unencumbered software released into the public domain.
|
||||
*
|
||||
* Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
* distribute this software, either in source code form or as a compiled
|
||||
* binary, for any purpose, commercial or non-commercial, and by any
|
||||
* means.
|
||||
|
||||
* In jurisdictions that recognize copyright laws, the author or authors
|
||||
* of this software dedicate any and all copyright interest in the
|
||||
* software to the public domain. We make this dedication for the benefit
|
||||
* of the public at large and to the detriment of our heirs and
|
||||
* successors. We intend this dedication to be an overt act of
|
||||
* relinquishment in perpetuity of all present and future rights to this
|
||||
* software under copyright law.
|
||||
*
|
||||
* 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 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.
|
||||
*
|
||||
* For more information, please refer to <http://unlicense.org>
|
||||
*
|
||||
*************************************************************************************************/
|
||||
|
||||
#include "basedefs.h"
|
||||
#include <stddef.h>
|
||||
|
||||
IW_EXTERN_C_START
|
||||
|
||||
/**
|
||||
* @brief Computes sha256 sum for given `input` data of `len` bytes.
|
||||
*
|
||||
* Limitations:
|
||||
* - Since input is a pointer in RAM, the data to hash should be in RAM, which could be a problem
|
||||
* for large data sizes.
|
||||
* - SHA algorithms theoretically operate on bit strings. However, this implementation has no support
|
||||
* for bit string lengths that are not multiples of eight, and it really operates on arrays of bytes.
|
||||
* In particular, the len parameter is a number of bytes.
|
||||
*
|
||||
* @param hash Hash sum placeholder
|
||||
* @param input
|
||||
* @param len
|
||||
*/
|
||||
IW_EXPORT void iwsha256(const void *input, size_t len, uint8_t hash_out[32]);
|
||||
|
||||
IW_EXPORT void iwsha256str(const void *input, size_t len, char str_out[65]);
|
||||
|
||||
IW_EXPORT void iwhash2str(uint8_t hash[32], char str_out[65]);
|
||||
|
||||
IW_EXTERN_C_END
|
||||
#endif
|
||||
@@ -1,411 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2011, Willem-Hendrik Thiart
|
||||
Copyright (c) 2012-2022 Softmotions Ltd <info@softmotions.com>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* 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.
|
||||
* The names of its contributors may not be used to endorse or promote
|
||||
products derived from this software without specific prior written
|
||||
permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL WILLEM-HENDRIK THIART 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.
|
||||
*/
|
||||
|
||||
#include "iwstree.h"
|
||||
#include "iwlog.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct tree_node_s {
|
||||
struct tree_node_s *left;
|
||||
struct tree_node_s *right;
|
||||
void *key;
|
||||
void *value;
|
||||
} tree_node_t;
|
||||
|
||||
|
||||
struct tree_iter_s {
|
||||
IWSTREE *st; /**< Owner tree */
|
||||
int spos; /**< Position of top element stack */
|
||||
int slen; /**< Max number of elements in stack */
|
||||
tree_node_t **stack; /**< Bottom of iterator stack */
|
||||
};
|
||||
|
||||
int iwstree_str_cmp(const void *o1, const void *o2) {
|
||||
return strcmp(o1, o2);
|
||||
}
|
||||
|
||||
int iwstree_uint64_cmp(const void *o1, const void *o2) {
|
||||
uint64_t v1 = *(uint64_t*) o1;
|
||||
uint64_t v2 = *(uint64_t*) o2;
|
||||
return v1 > v2 ? 1 : v1 < v2 ? -1 : 0;
|
||||
}
|
||||
|
||||
int iwstree_int64_cmp(const void *o1, const void *o2) {
|
||||
int64_t v1 = *(int64_t*) o1;
|
||||
int64_t v2 = *(int64_t*) o2;
|
||||
return v1 > v2 ? 1 : v1 < v2 ? -1 : 0;
|
||||
}
|
||||
|
||||
static int _cmp_default(const void *k1, const void *k2) {
|
||||
return k1 < k2 ? -1 : k1 > k2 ? 1 : 0;
|
||||
}
|
||||
|
||||
IWSTREE* iwstree_create(
|
||||
int (*cmp)(const void*, const void*),
|
||||
void (*kvfree)(void*, void*)) {
|
||||
IWSTREE *st;
|
||||
st = malloc(sizeof(IWSTREE));
|
||||
if (!st) {
|
||||
return 0;
|
||||
}
|
||||
memset(st, 0, sizeof(IWSTREE));
|
||||
if (!cmp) {
|
||||
cmp = _cmp_default;
|
||||
}
|
||||
st->cmp = cmp;
|
||||
st->kvfree = kvfree;
|
||||
return st;
|
||||
}
|
||||
|
||||
static void _free_node(IWSTREE *st, tree_node_t *node) {
|
||||
if (node) {
|
||||
_free_node(st, node->left);
|
||||
_free_node(st, node->right);
|
||||
if (st->kvfree) {
|
||||
st->kvfree(node->key, node->value);
|
||||
}
|
||||
free(node);
|
||||
}
|
||||
}
|
||||
|
||||
void iwstree_clear(IWSTREE *st) {
|
||||
if (st) {
|
||||
_free_node(st, st->root);
|
||||
st->root = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void iwstree_destroy(IWSTREE *st) {
|
||||
iwstree_clear(st);
|
||||
free(st);
|
||||
}
|
||||
|
||||
static tree_node_t* _init_node(void *key, void *value) {
|
||||
tree_node_t *n;
|
||||
n = malloc(sizeof(tree_node_t));
|
||||
if (!n) {
|
||||
return 0;
|
||||
}
|
||||
n->left = n->right = 0;
|
||||
n->key = key;
|
||||
n->value = value;
|
||||
return n;
|
||||
}
|
||||
|
||||
static void _rotate_right(tree_node_t **pa) {
|
||||
tree_node_t *child;
|
||||
child = (*pa)->left;
|
||||
assert(child);
|
||||
(*pa)->left = child->right;
|
||||
child->right = *pa;
|
||||
*pa = child;
|
||||
}
|
||||
|
||||
static void _rotate_left(tree_node_t **pa) {
|
||||
tree_node_t *child;
|
||||
child = (*pa)->right;
|
||||
assert(child);
|
||||
(*pa)->right = child->left;
|
||||
child->left = *pa;
|
||||
*pa = child;
|
||||
}
|
||||
|
||||
/**
|
||||
* bring this value to the top
|
||||
* */
|
||||
static tree_node_t* _splay(
|
||||
IWSTREE *st,
|
||||
int update_if_not_found,
|
||||
tree_node_t **gpa,
|
||||
tree_node_t **pa,
|
||||
tree_node_t **child,
|
||||
const void *key) {
|
||||
|
||||
int cmp;
|
||||
tree_node_t *next;
|
||||
|
||||
if (!(*child)) {
|
||||
return 0;
|
||||
}
|
||||
cmp = st->cmp((*child)->key, key);
|
||||
if (cmp == 0) {
|
||||
next = *child;
|
||||
} else if (cmp > 0) {
|
||||
next = _splay(st, update_if_not_found, pa, child, &(*child)->left, key);
|
||||
} else {
|
||||
next = _splay(st, update_if_not_found, pa, child, &(*child)->right, key);
|
||||
}
|
||||
if (!next) {
|
||||
if (update_if_not_found) {
|
||||
next = *child;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
if (next != *child) {
|
||||
return next;
|
||||
}
|
||||
}
|
||||
|
||||
if (!pa) {
|
||||
return next;
|
||||
}
|
||||
|
||||
if (!gpa) {
|
||||
/* zig left */
|
||||
if ((*pa)->left == next) {
|
||||
_rotate_right(pa);
|
||||
}
|
||||
/* zig right */
|
||||
else {
|
||||
_rotate_left(pa);
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
assert(gpa);
|
||||
|
||||
/* zig zig left */
|
||||
if (((*pa)->left == next) && ((*gpa)->left == *pa)) {
|
||||
_rotate_right(pa);
|
||||
_rotate_right(gpa);
|
||||
}
|
||||
/* zig zig right */
|
||||
else if (((*pa)->right == next) && ((*gpa)->right == *pa)) {
|
||||
_rotate_left(pa);
|
||||
_rotate_left(gpa);
|
||||
}
|
||||
/* zig zag right */
|
||||
else if (((*pa)->right == next) && ((*gpa)->left == *pa)) {
|
||||
_rotate_left(pa);
|
||||
_rotate_right(gpa);
|
||||
}
|
||||
/* zig zag left */
|
||||
else if (((*pa)->left == next) && ((*gpa)->right == *pa)) {
|
||||
_rotate_right(pa);
|
||||
_rotate_left(gpa);
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
int iwstree_is_empty(IWSTREE *st) {
|
||||
return st->root == 0;
|
||||
}
|
||||
|
||||
void* iwstree_remove(IWSTREE *st, const void *key) {
|
||||
tree_node_t *root, *tmp;
|
||||
void *val;
|
||||
|
||||
/* make removed node the root */
|
||||
if (!iwstree_get(st, key)) {
|
||||
return 0;
|
||||
}
|
||||
root = st->root;
|
||||
val = root->value;
|
||||
|
||||
assert(0 < st->count);
|
||||
if (root->left == 0) {
|
||||
st->root = root->right;
|
||||
} else {
|
||||
tmp = root->right;
|
||||
st->root = root->left;
|
||||
_splay(st, 1, 0, 0, (tree_node_t**) &st->root, key);
|
||||
((tree_node_t*) st->root)->right = tmp;
|
||||
}
|
||||
st->count--;
|
||||
assert(root != st->root);
|
||||
free(root);
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* get this item referred to by key. Slap it as root.
|
||||
*/
|
||||
void* iwstree_get(IWSTREE *st, const void *key) {
|
||||
tree_node_t *node = _splay(st, 0, 0, 0, (tree_node_t**) &st->root, key);
|
||||
return node ? node->value : 0;
|
||||
}
|
||||
|
||||
int iwstree_count(IWSTREE *st) {
|
||||
return st->count;
|
||||
}
|
||||
|
||||
void* iwstree_peek(IWSTREE *st) {
|
||||
return st->root ? ((tree_node_t*) st->root)->value : 0;
|
||||
}
|
||||
|
||||
static iwrc _iwstree_put(IWSTREE *st, void *key, void *value, bool overwrite) {
|
||||
tree_node_t *n;
|
||||
int cmp;
|
||||
if (!st->root) {
|
||||
st->root = _init_node(key, value);
|
||||
if (!st->root) {
|
||||
return iwrc_set_errno(IW_ERROR_ALLOC, errno);
|
||||
}
|
||||
st->count++;
|
||||
return 0;
|
||||
}
|
||||
n = _splay(st, 1, 0, 0, (tree_node_t**) &st->root, key);
|
||||
cmp = st->cmp(((tree_node_t*) st->root)->key, key);
|
||||
if (cmp != 0) {
|
||||
n = _init_node(key, value);
|
||||
if (!n) {
|
||||
return iwrc_set_errno(IW_ERROR_ALLOC, errno);
|
||||
}
|
||||
if (0 < cmp) {
|
||||
n->right = st->root;
|
||||
n->left = n->right->left;
|
||||
n->right->left = 0;
|
||||
} else {
|
||||
n->left = st->root;
|
||||
n->right = n->left->right;
|
||||
n->left->right = 0;
|
||||
}
|
||||
st->count++;
|
||||
} else if (overwrite) {
|
||||
if (n->value && st->kvfree) {
|
||||
st->kvfree(0, n->value);
|
||||
}
|
||||
n->value = value;
|
||||
}
|
||||
st->root = n;
|
||||
return 0;
|
||||
}
|
||||
|
||||
iwrc iwstree_put(IWSTREE *st, void *key, void *value) {
|
||||
return _iwstree_put(st, key, value, false);
|
||||
}
|
||||
|
||||
iwrc iwstree_put_overwrite(IWSTREE *st, void *key, void *value) {
|
||||
return _iwstree_put(st, key, value, true);
|
||||
}
|
||||
|
||||
static iwrc _iwstree_visit(tree_node_t *n, IWSTREE_VISITOR visitor, void *op) {
|
||||
iwrc rc = 0;
|
||||
if (!visitor(n->key, n->value, op, &rc) || rc) {
|
||||
return rc;
|
||||
}
|
||||
if (n->left) {
|
||||
rc = _iwstree_visit(n->left, visitor, op);
|
||||
RCRET(rc);
|
||||
}
|
||||
if (n->right) {
|
||||
rc = _iwstree_visit(n->right, visitor, op);
|
||||
RCRET(rc);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
iwrc iwstree_visit(IWSTREE *st, IWSTREE_VISITOR visitor, void *op) {
|
||||
if (st->root) {
|
||||
return _iwstree_visit(st->root, visitor, op);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define _ITER_STACK_AUNIT 32
|
||||
|
||||
static iwrc _iter_push(IWSTREE_ITER *iter, tree_node_t *n) {
|
||||
if (iter->spos + 1 > iter->slen) {
|
||||
void *np = realloc(iter->stack, (iter->slen + _ITER_STACK_AUNIT) * sizeof(*iter->stack));
|
||||
if (!np) {
|
||||
return iwrc_set_errno(IW_ERROR_ALLOC, errno);
|
||||
}
|
||||
iter->stack = np;
|
||||
iter->slen += _ITER_STACK_AUNIT;
|
||||
}
|
||||
iter->stack[iter->spos] = n;
|
||||
iter->spos++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static tree_node_t* _iter_pop(IWSTREE_ITER *iter) {
|
||||
if (iter->spos < 1) {
|
||||
return 0;
|
||||
}
|
||||
iter->spos--;
|
||||
return iter->stack[iter->spos];
|
||||
}
|
||||
|
||||
iwrc iwstree_iter_init(IWSTREE *st, IWSTREE_ITER *iter) {
|
||||
memset(iter, 0, sizeof(*iter));
|
||||
iter->st = st;
|
||||
tree_node_t *n = st->root;
|
||||
while (n) {
|
||||
iwrc rc = _iter_push(iter, n);
|
||||
RCRET(rc);
|
||||
n = n->left;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool iwstree_iter_has_next(IWSTREE_ITER *iter) {
|
||||
return iter->spos > 0;
|
||||
}
|
||||
|
||||
iwrc iwstree_iter_next(IWSTREE_ITER *iter, void **key, void **val) {
|
||||
if (key) {
|
||||
*key = 0;
|
||||
}
|
||||
if (val) {
|
||||
*val = 0;
|
||||
}
|
||||
if (iter->spos < 1) {
|
||||
return IW_ERROR_NOT_EXISTS;
|
||||
}
|
||||
tree_node_t *n = _iter_pop(iter);
|
||||
assert(n);
|
||||
if (key) {
|
||||
*key = n->key;
|
||||
}
|
||||
if (val) {
|
||||
*val = n->value;
|
||||
}
|
||||
if (n->right) {
|
||||
n = n->right;
|
||||
while (n) {
|
||||
iwrc rc = _iter_push(iter, n);
|
||||
RCRET(rc);
|
||||
n = n->left;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void iwstree_iter_close(IWSTREE_ITER *iter) {
|
||||
if (iter->stack) {
|
||||
free(iter->stack);
|
||||
}
|
||||
iter->slen = 0;
|
||||
iter->spos = 0;
|
||||
iter->stack = 0;
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
#pragma once
|
||||
#ifndef IWSTREE_H
|
||||
#define IWSTREE_H
|
||||
|
||||
/*
|
||||
Copyright (c) 2011, Willem-Hendrik Thiart
|
||||
Copyright (c) 2012-2022 Softmotions Ltd <info@softmotions.com>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* 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.
|
||||
* The names of its contributors may not be used to endorse or promote
|
||||
products derived from this software without specific prior written
|
||||
permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL WILLEM-HENDRIK THIART 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.
|
||||
*/
|
||||
|
||||
#include "basedefs.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
IW_EXTERN_C_START
|
||||
|
||||
typedef struct {
|
||||
void *root;
|
||||
int (*cmp)(const void*, const void*);
|
||||
void (*kvfree)(void*, void*);
|
||||
int count;
|
||||
} IWSTREE;
|
||||
|
||||
typedef struct _IWSTREE_ITER {
|
||||
IWSTREE *st; /**< Owner tree */
|
||||
int spos; /**< Position of top element stack */
|
||||
int slen; /**< Max number of elements in stack */
|
||||
void **stack; /**< Bottom of iterator stack */
|
||||
} IWSTREE_ITER;
|
||||
|
||||
typedef bool (*IWSTREE_VISITOR)(void *key, void *val, void *op, iwrc *rcp);
|
||||
|
||||
/**
|
||||
* @brief Constructs new splay tree
|
||||
*
|
||||
* @param cmp Keys compare function. If zero address pointers will be compared.
|
||||
* @param kvfree Optional `(key, value)` free function
|
||||
* @return IWSTREE* or NULL if memory allocation failed
|
||||
*/
|
||||
IW_EXPORT IW_ALLOC IWSTREE *iwstree_create(
|
||||
int (*cmp)(const void*, const void*),
|
||||
void (*kvfree)(void*, void*)
|
||||
);
|
||||
|
||||
IW_EXPORT int iwstree_str_cmp(const void *o1, const void *o2);
|
||||
|
||||
IW_EXPORT int iwstree_uint64_cmp(const void *o1, const void *o2);
|
||||
|
||||
IW_EXPORT int iwstree_int64_cmp(const void *o1, const void *o2);
|
||||
|
||||
IW_EXPORT void iwstree_clear(IWSTREE *st);
|
||||
|
||||
IW_EXPORT void iwstree_destroy(IWSTREE *st);
|
||||
|
||||
IW_EXPORT int iwstree_is_empty(IWSTREE *st);
|
||||
|
||||
IW_EXPORT void *iwstree_remove(IWSTREE *st, const void *key);
|
||||
|
||||
IW_EXPORT void *iwstree_get(IWSTREE *st, const void *key);
|
||||
|
||||
IW_EXPORT int iwstree_count(IWSTREE *st);
|
||||
|
||||
IW_EXPORT void *iwstree_peek(IWSTREE *st);
|
||||
|
||||
IW_EXPORT iwrc iwstree_put(IWSTREE *st, void *key, void *value);
|
||||
|
||||
IW_EXPORT iwrc iwstree_put_overwrite(IWSTREE *st, void *key, void *value);
|
||||
|
||||
IW_EXPORT iwrc iwstree_visit(IWSTREE *st, IWSTREE_VISITOR visitor, void *op);
|
||||
|
||||
IW_EXPORT iwrc iwstree_iter_init(IWSTREE *st, IWSTREE_ITER *iter);
|
||||
|
||||
IW_EXPORT bool iwstree_iter_has_next(IWSTREE_ITER *iter);
|
||||
|
||||
IW_EXPORT iwrc iwstree_iter_next(IWSTREE_ITER *iter, void **key, void **val);
|
||||
|
||||
IW_EXPORT void iwstree_iter_close(IWSTREE_ITER *iter);
|
||||
|
||||
IW_EXTERN_C_END
|
||||
#endif
|
||||
+69
-15
@@ -8,16 +8,17 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
struct _TASK {
|
||||
struct task {
|
||||
iwstw_task_f fn;
|
||||
void *arg;
|
||||
struct _TASK *next;
|
||||
struct task *next;
|
||||
};
|
||||
|
||||
struct _IWSTW {
|
||||
struct _TASK *head;
|
||||
struct _TASK *tail;
|
||||
struct iwstw {
|
||||
struct task *head;
|
||||
struct task *tail;
|
||||
char *thread_name;
|
||||
iwstw_on_task_discard_f on_task_discard;
|
||||
pthread_mutex_t mtx;
|
||||
pthread_cond_t cond;
|
||||
pthread_cond_t cond_queue;
|
||||
@@ -30,7 +31,7 @@ struct _IWSTW {
|
||||
};
|
||||
|
||||
static void* _worker_fn(void *op) {
|
||||
struct _IWSTW *stw = op;
|
||||
struct iwstw *stw = op;
|
||||
assert(stw);
|
||||
|
||||
if (stw->thread_name) {
|
||||
@@ -43,7 +44,7 @@ static void* _worker_fn(void *op) {
|
||||
|
||||
pthread_mutex_lock(&stw->mtx);
|
||||
if (stw->head) {
|
||||
struct _TASK *h = stw->head;
|
||||
struct task *h = stw->head;
|
||||
fn = h->fn;
|
||||
arg = h->arg;
|
||||
stw->head = h->next;
|
||||
@@ -96,10 +97,13 @@ iwrc iwstw_shutdown(IWSTW *stwp, bool wait_for_all) {
|
||||
return IW_ERROR_ASSERTION;
|
||||
}
|
||||
if (!wait_for_all) {
|
||||
struct _TASK *t = stw->head;
|
||||
struct task *t = stw->head;
|
||||
while (t) {
|
||||
struct _TASK *o = t;
|
||||
struct task *o = t;
|
||||
t = t->next;
|
||||
if (stw->on_task_discard) {
|
||||
stw->on_task_discard(t->fn, t->arg);
|
||||
}
|
||||
free(o);
|
||||
}
|
||||
stw->head = 0;
|
||||
@@ -135,9 +139,9 @@ iwrc iwstw_schedule(IWSTW stw, iwstw_task_f fn, void *arg) {
|
||||
return IW_ERROR_INVALID_ARGS;
|
||||
}
|
||||
iwrc rc = 0;
|
||||
struct _TASK *task = malloc(sizeof(*task));
|
||||
struct task *task = malloc(sizeof(*task));
|
||||
RCA(task, finish);
|
||||
*task = (struct _TASK) {
|
||||
*task = (struct task) {
|
||||
.fn = fn,
|
||||
.arg = arg
|
||||
};
|
||||
@@ -186,15 +190,61 @@ finish:
|
||||
return rc; // NOLINT (clang-analyzer-unix.Malloc)
|
||||
}
|
||||
|
||||
iwrc iwstw_schedule_only(IWSTW stw, iwstw_task_f fn, void *arg) {
|
||||
if (!stw || !fn) {
|
||||
return IW_ERROR_INVALID_ARGS;
|
||||
}
|
||||
iwrc rc = 0;
|
||||
struct task *task = malloc(sizeof(*task));
|
||||
RCA(task, finish);
|
||||
*task = (struct task) {
|
||||
.fn = fn,
|
||||
.arg = arg
|
||||
};
|
||||
int rci = pthread_mutex_lock(&stw->mtx);
|
||||
if (rci) {
|
||||
rc = iwrc_set_errno(IW_ERROR_THREADING_ERRNO, errno);
|
||||
goto finish;
|
||||
}
|
||||
if (stw->shutdown) {
|
||||
rc = IW_ERROR_INVALID_STATE;
|
||||
pthread_mutex_unlock(&stw->mtx);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
struct task *t = stw->head;
|
||||
while (t) {
|
||||
struct task *o = t;
|
||||
t = t->next;
|
||||
if (stw->on_task_discard) {
|
||||
stw->on_task_discard(t->fn, t->arg);
|
||||
}
|
||||
free(o);
|
||||
}
|
||||
|
||||
stw->head = task;
|
||||
stw->tail = task;
|
||||
stw->cnt = 1;
|
||||
|
||||
pthread_cond_broadcast(&stw->cond);
|
||||
pthread_mutex_unlock(&stw->mtx);
|
||||
|
||||
finish:
|
||||
if (rc) {
|
||||
free(task);
|
||||
}
|
||||
return rc; // NOLINT (clang-analyzer-unix.Malloc)
|
||||
}
|
||||
|
||||
iwrc iwstw_schedule_empty_only(IWSTW stw, iwstw_task_f fn, void *arg, bool *out_scheduled) {
|
||||
if (!stw || !fn || !out_scheduled) {
|
||||
return IW_ERROR_INVALID_ARGS;
|
||||
}
|
||||
*out_scheduled = false;
|
||||
iwrc rc = 0;
|
||||
struct _TASK *task = malloc(sizeof(*task));
|
||||
struct task *task = malloc(sizeof(*task));
|
||||
RCA(task, finish);
|
||||
*task = (struct _TASK) {
|
||||
*task = (struct task) {
|
||||
.fn = fn,
|
||||
.arg = arg
|
||||
};
|
||||
@@ -226,6 +276,10 @@ finish:
|
||||
return rc; // NOLINT (clang-analyzer-unix.Malloc)
|
||||
}
|
||||
|
||||
void iwstw_set_on_task_discard(IWSTW stw, iwstw_on_task_discard_f on_task_discard) {
|
||||
stw->on_task_discard = on_task_discard;
|
||||
}
|
||||
|
||||
iwrc iwstw_start(const char *thread_name, int queue_limit, bool queue_blocking, IWSTW *out_stw) {
|
||||
if (queue_limit < 0 || !out_stw) {
|
||||
return IW_ERROR_INVALID_ARGS;
|
||||
@@ -233,14 +287,14 @@ iwrc iwstw_start(const char *thread_name, int queue_limit, bool queue_blocking,
|
||||
if (thread_name && strlen(thread_name) > 15) {
|
||||
return IW_ERROR_INVALID_ARGS;
|
||||
}
|
||||
struct _IWSTW *stw = malloc(sizeof(*stw));
|
||||
struct iwstw *stw = malloc(sizeof(*stw));
|
||||
if (!stw) {
|
||||
*out_stw = 0;
|
||||
return iwrc_set_errno(IW_ERROR_ALLOC, errno);
|
||||
}
|
||||
int rci;
|
||||
iwrc rc = 0;
|
||||
*stw = (struct _IWSTW) {
|
||||
*stw = (struct iwstw) {
|
||||
.queue_limit = queue_limit,
|
||||
.mtx = PTHREAD_MUTEX_INITIALIZER,
|
||||
.cond = PTHREAD_COND_INITIALIZER,
|
||||
|
||||
+16
-2
@@ -34,14 +34,16 @@
|
||||
|
||||
IW_EXTERN_C_START
|
||||
|
||||
struct _IWSTW;
|
||||
typedef struct _IWSTW*IWSTW;
|
||||
struct iwstw;
|
||||
typedef struct iwstw*IWSTW;
|
||||
|
||||
/**
|
||||
* @brief Task to execute
|
||||
*/
|
||||
typedef void (*iwstw_task_f)(void *arg);
|
||||
|
||||
typedef void (*iwstw_on_task_discard_f)(iwstw_task_f task, void *arg);
|
||||
|
||||
/**
|
||||
* @brief Starts a single thread worker.
|
||||
* Function will block until start of worker thread.
|
||||
@@ -74,11 +76,23 @@ IW_EXPORT iwrc iwstw_shutdown(IWSTW *stwp, bool wait_for_all);
|
||||
*/
|
||||
IW_EXPORT iwrc iwstw_schedule(IWSTW stw, iwstw_task_f task, void *task_arg);
|
||||
|
||||
/**
|
||||
* @brief Schedule task for execution discading all pending tasks on queue.
|
||||
* @note If worker is in process of stopping `IW_ERROR_INVALID_STATE` will be returned.
|
||||
*/
|
||||
IW_EXPORT iwrc iwstw_schedule_only(IWSTW stw, iwstw_task_f task, void *task_arg);
|
||||
|
||||
/**
|
||||
* @brief Schedule task only if task queue is empty.
|
||||
*/
|
||||
IW_EXPORT iwrc iwstw_schedule_empty_only(IWSTW stw, iwstw_task_f task, void *task_arg, bool *out_scheduled);
|
||||
|
||||
/**
|
||||
* @brief Set on task discard callback function.
|
||||
* Called when pending task removed from queue and will not be executed.
|
||||
*/
|
||||
IW_EXPORT void iwstw_set_on_task_discard(IWSTW stw, iwstw_on_task_discard_f on_task_discard);
|
||||
|
||||
/**
|
||||
* @brief Returns size of tasks queue.
|
||||
*/
|
||||
|
||||
+38
-3
@@ -1,5 +1,37 @@
|
||||
#include "iwth.h"
|
||||
#include "iwp.h"
|
||||
#include "iwlog.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
|
||||
iwrc iw_cond_timed_wait_ms(pthread_cond_t *cond, pthread_mutex_t *mtx, long timeout_ms, bool *out_is_timeout) {
|
||||
iwrc rc;
|
||||
int rci;
|
||||
struct timespec tp;
|
||||
*out_is_timeout = false;
|
||||
|
||||
#if defined(IW_HAVE_CLOCK_MONOTONIC) && defined(IW_HAVE_PTHREAD_CONDATTR_SETCLOCK)
|
||||
rc = iwp_clock_get_time(CLOCK_MONOTONIC, &tp);
|
||||
#else
|
||||
rc = iwp_clock_get_time(CLOCK_REALTIME, &tp);
|
||||
#endif
|
||||
RCRET(rc);
|
||||
tp.tv_sec += timeout_ms / 1000;
|
||||
tp.tv_nsec += (timeout_ms % 1000) * 1000000;
|
||||
do {
|
||||
rci = pthread_cond_timedwait(cond, mtx, &tp);
|
||||
} while (rci == EINTR);
|
||||
if (rci) {
|
||||
if (rci == ETIMEDOUT) {
|
||||
*out_is_timeout = true;
|
||||
} else {
|
||||
rc = iwrc_set_errno(IW_ERROR_THREADING_ERRNO, rci);
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
@@ -17,14 +49,16 @@ int pthread_barrierattr_destroy(pthread_barrierattr_t *attr __unused) {
|
||||
|
||||
int pthread_barrierattr_getpshared(
|
||||
const pthread_barrierattr_t* restrict attr __unused,
|
||||
int* restrict pshared) {
|
||||
int* restrict pshared
|
||||
) {
|
||||
*pshared = PTHREAD_PROCESS_PRIVATE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pthread_barrierattr_setpshared(
|
||||
pthread_barrierattr_t *attr __unused,
|
||||
int pshared) {
|
||||
int pshared
|
||||
) {
|
||||
if (pshared != PTHREAD_PROCESS_PRIVATE) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
@@ -35,7 +69,8 @@ int pthread_barrierattr_setpshared(
|
||||
int pthread_barrier_init(
|
||||
pthread_barrier_t* restrict barrier,
|
||||
const pthread_barrierattr_t* restrict attr __unused,
|
||||
unsigned count) {
|
||||
unsigned count
|
||||
) {
|
||||
if (count == 0) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
|
||||
@@ -31,6 +31,12 @@
|
||||
#include "basedefs.h"
|
||||
#include <pthread.h>
|
||||
|
||||
IW_EXPORT iwrc iw_cond_timed_wait_ms(
|
||||
pthread_cond_t *cond,
|
||||
pthread_mutex_t *mtx,
|
||||
long timeout_ms,
|
||||
bool *out_is_timeout);
|
||||
|
||||
#if defined(__APPLE__) || (defined(__ANDROID_API__) && __ANDROID_API__ < 24)
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
+154
-65
@@ -2,57 +2,59 @@
|
||||
#include "iwth.h"
|
||||
#include "iwp.h"
|
||||
#include "iwlog.h"
|
||||
#include "iwarr.h"
|
||||
#include "iwp.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
struct _TASK {
|
||||
struct task {
|
||||
iwtp_task_f fn;
|
||||
void *arg;
|
||||
struct _TASK *next;
|
||||
struct task *next;
|
||||
};
|
||||
|
||||
struct _IWTP {
|
||||
struct _TASK *head;
|
||||
struct _TASK *tail;
|
||||
struct iwtp {
|
||||
struct task *head;
|
||||
struct task *tail;
|
||||
pthread_mutex_t mtx;
|
||||
pthread_cond_t cond;
|
||||
pthread_t *threads;
|
||||
IWULIST threads;
|
||||
|
||||
char *thread_name_prefix;
|
||||
int num_threads;
|
||||
int num_threads_busy;
|
||||
int overflow_threads_factor;
|
||||
int queue_limit;
|
||||
int cnt;
|
||||
volatile bool shutdown;
|
||||
int queue_size;
|
||||
|
||||
bool warn_on_overflow_thread_spawn;
|
||||
bool shutdown;
|
||||
};
|
||||
|
||||
static void* _worker_fn(void *op);
|
||||
|
||||
iwrc iwtp_schedule(IWTP tp, iwtp_task_f fn, void *arg) {
|
||||
if (!tp || !fn) {
|
||||
return IW_ERROR_INVALID_ARGS;
|
||||
}
|
||||
|
||||
iwrc rc = 0;
|
||||
struct _TASK *task = malloc(sizeof(*task));
|
||||
struct task *task = malloc(sizeof(*task));
|
||||
RCA(task, finish);
|
||||
|
||||
*task = (struct _TASK) {
|
||||
*task = (struct task) {
|
||||
.fn = fn,
|
||||
.arg = arg
|
||||
};
|
||||
|
||||
int rci = pthread_mutex_lock(&tp->mtx);
|
||||
if (rci) {
|
||||
rc = iwrc_set_errno(IW_ERROR_THREADING_ERRNO, errno);
|
||||
goto finish;
|
||||
}
|
||||
if (tp->shutdown) {
|
||||
rc = IW_ERROR_INVALID_STATE;
|
||||
pthread_mutex_unlock(&tp->mtx);
|
||||
goto finish;
|
||||
}
|
||||
if (tp->queue_limit && (tp->cnt + 1 > tp->queue_limit)) {
|
||||
pthread_mutex_lock(&tp->mtx);
|
||||
if (tp->queue_limit && (tp->queue_size + 1 > tp->queue_limit)) {
|
||||
rc = IW_ERROR_OVERFLOW;
|
||||
pthread_mutex_unlock(&tp->mtx);
|
||||
iwlog_error("iwtp | Reached thread pool queue size limit: %d", tp->queue_limit);
|
||||
goto finish;
|
||||
}
|
||||
if (tp->tail) {
|
||||
@@ -62,7 +64,18 @@ iwrc iwtp_schedule(IWTP tp, iwtp_task_f fn, void *arg) {
|
||||
tp->head = task;
|
||||
tp->tail = task;
|
||||
}
|
||||
++tp->cnt;
|
||||
++tp->queue_size;
|
||||
|
||||
if ( tp->queue_size > 1
|
||||
&& tp->num_threads_busy >= tp->num_threads
|
||||
&& iwulist_length(&tp->threads) < tp->num_threads * (1 + tp->overflow_threads_factor)) {
|
||||
pthread_t th;
|
||||
int rci = pthread_create(&th, 0, _worker_fn, tp);
|
||||
if (rci) {
|
||||
iwlog_ecode_error2(iwrc_set_errno(IW_ERROR_THREADING_ERRNO, rci), "iwtp | Failed to create and overflow thread");
|
||||
}
|
||||
}
|
||||
|
||||
pthread_cond_signal(&tp->cond);
|
||||
pthread_mutex_unlock(&tp->mtx);
|
||||
|
||||
@@ -74,19 +87,31 @@ finish:
|
||||
}
|
||||
|
||||
static void* _worker_fn(void *op) {
|
||||
struct _IWTP *tp = op;
|
||||
struct iwtp *tp = op;
|
||||
assert(tp);
|
||||
|
||||
pthread_t st = pthread_self();
|
||||
|
||||
pthread_mutex_lock(&tp->mtx);
|
||||
size_t idx = iwulist_length(&tp->threads);
|
||||
if (iwulist_push(&tp->threads, &st)) {
|
||||
pthread_mutex_unlock(&tp->mtx);
|
||||
return 0;
|
||||
}
|
||||
pthread_mutex_unlock(&tp->mtx);
|
||||
|
||||
if (tp->thread_name_prefix) {
|
||||
pthread_t st = pthread_self();
|
||||
for (int i = 0; i < tp->num_threads; ++i) {
|
||||
if (tp->threads[i] == st) {
|
||||
char nbuf[strlen(tp->thread_name_prefix) + 16];
|
||||
snprintf(nbuf, sizeof(nbuf), "%s%d", tp->thread_name_prefix, i);
|
||||
iwp_set_current_thread_name(nbuf);
|
||||
break;
|
||||
char nbuf[64];
|
||||
if (idx >= tp->num_threads) {
|
||||
snprintf(nbuf, sizeof(nbuf), "%s%zd+", tp->thread_name_prefix, idx);
|
||||
if (tp->warn_on_overflow_thread_spawn) {
|
||||
iwlog_warn("iwtp | Overflow thread spawned: %s%zd+",
|
||||
tp->thread_name_prefix ? tp->thread_name_prefix : "", idx);
|
||||
}
|
||||
} else {
|
||||
snprintf(nbuf, sizeof(nbuf), "%s%zd", tp->thread_name_prefix, idx);
|
||||
}
|
||||
iwp_set_current_thread_name(nbuf);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
@@ -94,15 +119,16 @@ static void* _worker_fn(void *op) {
|
||||
iwtp_task_f fn = 0;
|
||||
|
||||
pthread_mutex_lock(&tp->mtx);
|
||||
++tp->num_threads_busy;
|
||||
if (tp->head) {
|
||||
struct _TASK *h = tp->head;
|
||||
struct task *h = tp->head;
|
||||
fn = h->fn;
|
||||
arg = h->arg;
|
||||
tp->head = h->next;
|
||||
if (tp->head == 0) {
|
||||
tp->tail = 0;
|
||||
}
|
||||
--tp->cnt;
|
||||
--tp->queue_size;
|
||||
free(h);
|
||||
}
|
||||
pthread_mutex_unlock(&tp->mtx);
|
||||
@@ -112,6 +138,18 @@ static void* _worker_fn(void *op) {
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&tp->mtx);
|
||||
--tp->num_threads_busy;
|
||||
|
||||
if (idx >= tp->num_threads) {
|
||||
// Overflow thread will be terminated immediately.
|
||||
if (!tp->shutdown) {
|
||||
iwulist_remove_first_by(&tp->threads, &st);
|
||||
pthread_detach(st);
|
||||
}
|
||||
pthread_mutex_unlock(&tp->mtx);
|
||||
break;
|
||||
}
|
||||
|
||||
if (tp->head) {
|
||||
pthread_mutex_unlock(&tp->mtx);
|
||||
continue;
|
||||
@@ -119,41 +157,87 @@ static void* _worker_fn(void *op) {
|
||||
pthread_mutex_unlock(&tp->mtx);
|
||||
break;
|
||||
}
|
||||
|
||||
pthread_cond_wait(&tp->cond, &tp->mtx);
|
||||
pthread_mutex_unlock(&tp->mtx);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
iwrc iwtp_start(const char *thread_name_prefix, int num_threads, int queue_limit, IWTP *out_tp) {
|
||||
if (num_threads < 1 || num_threads > 1023 || queue_limit < 0 || !out_tp) {
|
||||
iwrc iwtp_start_by_spec(const struct iwtp_spec *spec, IWTP *out_tp) {
|
||||
iwrc rc = 0;
|
||||
if (!spec || !out_tp) {
|
||||
return IW_ERROR_INVALID_ARGS;
|
||||
}
|
||||
if (thread_name_prefix && strlen(thread_name_prefix) > 15) {
|
||||
if (spec->thread_name_prefix && strlen(spec->thread_name_prefix) > 15) {
|
||||
return IW_ERROR_INVALID_ARGS;
|
||||
}
|
||||
|
||||
struct _IWTP *tp = malloc(sizeof(*tp) + sizeof(pthread_t) * num_threads);
|
||||
if (!tp) {
|
||||
*out_tp = 0;
|
||||
return iwrc_set_errno(IW_ERROR_ALLOC, errno);
|
||||
int num_threads = spec->num_threads;
|
||||
if (num_threads < 1) {
|
||||
num_threads = iwp_num_cpu_cores();
|
||||
} else if (num_threads > 1023) {
|
||||
num_threads = 1024;
|
||||
}
|
||||
*tp = (struct _IWTP) {
|
||||
|
||||
int queue_limit = spec->queue_limit;
|
||||
if (queue_limit < 1) {
|
||||
queue_limit = 0;
|
||||
}
|
||||
|
||||
int overflow_threads_factor = spec->overflow_threads_factor;
|
||||
if (overflow_threads_factor > 2) {
|
||||
overflow_threads_factor = 2;
|
||||
}
|
||||
|
||||
struct iwtp *tp = malloc(sizeof(*tp));
|
||||
if (!tp) {
|
||||
rc = iwrc_set_errno(IW_ERROR_ALLOC, errno);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
*tp = (struct iwtp) {
|
||||
.warn_on_overflow_thread_spawn = spec->warn_on_overflow_thread_spawn,
|
||||
.overflow_threads_factor = overflow_threads_factor,
|
||||
.num_threads = num_threads,
|
||||
.queue_limit = queue_limit,
|
||||
.mtx = PTHREAD_MUTEX_INITIALIZER,
|
||||
.cond = PTHREAD_COND_INITIALIZER
|
||||
};
|
||||
if (thread_name_prefix) {
|
||||
tp->thread_name_prefix = strdup(thread_name_prefix);
|
||||
|
||||
if (spec->thread_name_prefix) {
|
||||
tp->thread_name_prefix = strdup(spec->thread_name_prefix);
|
||||
}
|
||||
tp->threads = (void*) ((char*) tp + sizeof(*tp));
|
||||
memset(tp->threads, 0, sizeof(pthread_t) * num_threads);
|
||||
while (num_threads--) {
|
||||
pthread_create(&tp->threads[num_threads], 0, _worker_fn, tp);
|
||||
|
||||
RCC(rc, finish, iwulist_init(&tp->threads, num_threads, sizeof(pthread_t)));
|
||||
|
||||
for (size_t i = 0; i < num_threads; ++i) {
|
||||
pthread_t th;
|
||||
int rci = pthread_create(&th, 0, _worker_fn, tp);
|
||||
if (rci) {
|
||||
rc = iwrc_set_errno(IW_ERROR_THREADING_ERRNO, rci);
|
||||
iwlog_ecode_error3(rc);
|
||||
goto finish;
|
||||
}
|
||||
}
|
||||
*out_tp = tp;
|
||||
return 0;
|
||||
|
||||
finish:
|
||||
if (IW_UNLIKELY(rc)) {
|
||||
*out_tp = 0;
|
||||
iwtp_shutdown(&tp, false);
|
||||
} else {
|
||||
*out_tp = tp;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
iwrc iwtp_start(const char *thread_name_prefix, int num_threads, int queue_limit, IWTP *out_tp) {
|
||||
return iwtp_start_by_spec(&(struct iwtp_spec) {
|
||||
.thread_name_prefix = thread_name_prefix,
|
||||
.num_threads = num_threads,
|
||||
.queue_limit = queue_limit
|
||||
}, out_tp);
|
||||
}
|
||||
|
||||
iwrc iwtp_shutdown(IWTP *tpp, bool wait_for_all) {
|
||||
@@ -161,51 +245,56 @@ iwrc iwtp_shutdown(IWTP *tpp, bool wait_for_all) {
|
||||
return 0;
|
||||
}
|
||||
IWTP tp = *tpp;
|
||||
IWULIST *joinlist = 0;
|
||||
|
||||
pthread_mutex_lock(&tp->mtx);
|
||||
pthread_t st = pthread_self();
|
||||
if (iwulist_find_first(&tp->threads, &st) != -1) {
|
||||
pthread_mutex_unlock(&tp->mtx);
|
||||
iwlog_error("iwtp | Calling iwtp_shutdown() from one of managed thread: %lu", (unsigned long) st);
|
||||
return IW_ERROR_ASSERTION;
|
||||
}
|
||||
|
||||
if (tp->shutdown) {
|
||||
pthread_mutex_unlock(&tp->mtx);
|
||||
return 0;
|
||||
}
|
||||
*tpp = 0;
|
||||
tp->shutdown = true;
|
||||
pthread_t st = pthread_self();
|
||||
for (int i = 0; i < tp->num_threads; ++i) {
|
||||
if (tp->threads[i] == st) {
|
||||
pthread_mutex_unlock(&tp->mtx);
|
||||
iwlog_error("iwtp | Thread iwtp_shutdown() from one of pool thread: %lu", (unsigned long) st);
|
||||
return IW_ERROR_ASSERTION;
|
||||
}
|
||||
}
|
||||
|
||||
if (!wait_for_all) {
|
||||
struct _TASK *t = tp->head;
|
||||
struct task *t = tp->head;
|
||||
while (t) {
|
||||
struct _TASK *o = t;
|
||||
struct task *o = t;
|
||||
t = t->next;
|
||||
free(o);
|
||||
}
|
||||
tp->head = 0;
|
||||
tp->tail = 0;
|
||||
tp->cnt = 0;
|
||||
tp->queue_size = 0;
|
||||
}
|
||||
joinlist = iwulist_clone(&tp->threads);
|
||||
pthread_cond_broadcast(&tp->cond);
|
||||
pthread_mutex_unlock(&tp->mtx);
|
||||
|
||||
for (int i = 0; i < tp->num_threads; ++i) {
|
||||
if (tp->threads[i]) {
|
||||
pthread_join(tp->threads[i], 0);
|
||||
}
|
||||
for (size_t i = 0, l = iwulist_length(joinlist); i < l; ++i) {
|
||||
pthread_t t = *(pthread_t*) iwulist_at2(joinlist, i);
|
||||
pthread_join(t, 0);
|
||||
}
|
||||
|
||||
pthread_cond_destroy(&tp->cond);
|
||||
pthread_mutex_destroy(&tp->mtx);
|
||||
iwulist_destroy_keep(&tp->threads);
|
||||
iwulist_destroy(&joinlist);
|
||||
free(tp->thread_name_prefix);
|
||||
free(tp);
|
||||
*tpp = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int iwtp_queue_size(IWTP tp) {
|
||||
int res = 0;
|
||||
pthread_mutex_lock(&tp->mtx);
|
||||
res = tp->cnt;
|
||||
res = tp->queue_size;
|
||||
pthread_mutex_unlock(&tp->mtx);
|
||||
return res;
|
||||
}
|
||||
|
||||
+37
-2
@@ -34,14 +34,49 @@
|
||||
|
||||
IW_EXTERN_C_START
|
||||
|
||||
struct _IWTP;
|
||||
typedef struct _IWTP*IWTP;
|
||||
struct iwtp;
|
||||
typedef struct iwtp*IWTP;
|
||||
|
||||
struct iwtp_spec {
|
||||
/** Optional thread name prefix in thread pool.
|
||||
* @note Thread name length must be not greater then 15 characters.
|
||||
*/
|
||||
const char *thread_name_prefix;
|
||||
|
||||
/** Number of hot threads in thread pool.
|
||||
* Threads are allocated on when thread pool created.
|
||||
* @note Value must be in rage [1-1024].
|
||||
* @note If zero then value will be set to number of cpu cores.
|
||||
*/
|
||||
int num_threads;
|
||||
|
||||
/** Maximum number of tasks in queue.
|
||||
Zero for unlimited queue. */
|
||||
int queue_limit;
|
||||
|
||||
/** If task queue is full and the `overflow_threads_factor` is not zero
|
||||
* then pool is allowed to spawn extra threads to process tasks as long
|
||||
* as overall number of threads less of equal to `num_threads * overflow_threads_factor`
|
||||
* @note Max: 2
|
||||
*/
|
||||
int overflow_threads_factor;
|
||||
|
||||
/**
|
||||
* It true performs log warning in the case of spawning overflow thread.
|
||||
*/
|
||||
bool warn_on_overflow_thread_spawn;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Task to execute
|
||||
*/
|
||||
typedef void (*iwtp_task_f)(void *arg);
|
||||
|
||||
/**
|
||||
* @brief Creates a new thread pool instance using provided `spec` config.
|
||||
*/
|
||||
IW_EXPORT iwrc iwtp_start_by_spec(const struct iwtp_spec *spec, IWTP *out_tp);
|
||||
|
||||
/**
|
||||
* @brief Creates a new thread pool instance.
|
||||
* @param num_threads Number of threads in the pool, accepted values in range `[1-1024]`
|
||||
|
||||
+39
-19
@@ -29,6 +29,8 @@
|
||||
#include "iwcfg.h"
|
||||
#include "iwutils.h"
|
||||
#include "iwlog.h"
|
||||
#include "iwxstr.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
@@ -104,7 +106,6 @@ int iwlog2_64(uint64_t val) {
|
||||
}
|
||||
|
||||
uint32_t iwu_crc32(const uint8_t *buf, int len, uint32_t init) {
|
||||
|
||||
static const unsigned int crc32_table[] = {
|
||||
0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9,
|
||||
0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
|
||||
@@ -224,28 +225,47 @@ int iwu_cmp_files(FILE *f1, FILE *f2, bool verbose) {
|
||||
return (c1 - c2);
|
||||
}
|
||||
|
||||
char* iwu_file_read_as_buf(const char *path) {
|
||||
struct stat st;
|
||||
if (stat(path, &st) == -1) {
|
||||
char* iwu_file_read_as_buf_len(const char *path, size_t *out_len) {
|
||||
IWXSTR *xstr = iwxstr_new();
|
||||
if (!xstr) {
|
||||
*out_len = 0;
|
||||
return 0;
|
||||
}
|
||||
ssize_t rb, rc = 0;
|
||||
char buf[8192];
|
||||
int fd = open(path, O_RDONLY | O_CLOEXEC);
|
||||
if (fd == -1) {
|
||||
if (fd < 0) {
|
||||
iwxstr_destroy(xstr);
|
||||
return 0;
|
||||
}
|
||||
while (1) {
|
||||
rb = read(fd, buf, sizeof(buf));
|
||||
if (rb > 0) {
|
||||
if (iwxstr_cat(xstr, buf, rb)) {
|
||||
goto error;
|
||||
}
|
||||
rc += rb;
|
||||
} else if (rb < 0) {
|
||||
if (errno != EINTR) {
|
||||
goto error;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
char *data = malloc(st.st_size + 1);
|
||||
if (!data) {
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
if (st.st_size != read(fd, data, st.st_size)) {
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
close(fd);
|
||||
data[st.st_size] = '\0';
|
||||
return data;
|
||||
*out_len = rc;
|
||||
return iwxstr_destroy_keep_ptr(xstr);
|
||||
|
||||
error:
|
||||
*out_len = 0;
|
||||
iwxstr_destroy(xstr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
char* iwu_file_read_as_buf(const char *path) {
|
||||
size_t sz;
|
||||
return iwu_file_read_as_buf_len(path, &sz);
|
||||
}
|
||||
|
||||
uint32_t iwu_x31_u32_hash(const char *s) {
|
||||
@@ -265,8 +285,8 @@ iwrc iwu_replace(
|
||||
const char *keys[],
|
||||
int keysz,
|
||||
iwu_replace_mapper mapper,
|
||||
void *mapper_op) {
|
||||
|
||||
void *mapper_op
|
||||
) {
|
||||
if (!result || !data || !keys || !mapper) {
|
||||
return IW_ERROR_INVALID_ARGS;
|
||||
}
|
||||
|
||||
+5
-3
@@ -240,7 +240,7 @@ IW_EXPORT uint32_t iwu_crc32(const uint8_t *buf, int len, uint32_t init);
|
||||
/**
|
||||
* @brief Replaces a char @a sch with @a rch in a null terminated @a data char buffer.
|
||||
*/
|
||||
IW_EXPORT char *iwu_replace_char(char *data, char sch, char rch);
|
||||
IW_EXPORT char* iwu_replace_char(char *data, char sch, char rch);
|
||||
|
||||
/**
|
||||
* @brief Returns `\0` terminated string as replacement
|
||||
@@ -257,7 +257,7 @@ typedef const char* (*iwu_replace_mapper)(const char *key, void *op);
|
||||
* @param datalen Length of data buffer
|
||||
* @param keys Array of keys to search
|
||||
* @param keysz Number of elements in keys array.
|
||||
* Negative for NULL terminated arrays.
|
||||
* Negative for NULL terminated arrays.
|
||||
* @param mapper Replacement mapper
|
||||
* @param mapper_op Replacement mapper opaque data
|
||||
*/
|
||||
@@ -272,7 +272,9 @@ IW_EXPORT iwrc iwu_replace(
|
||||
|
||||
IW_EXPORT int iwu_cmp_files(FILE *f1, FILE *f2, bool verbose);
|
||||
|
||||
IW_EXPORT char *iwu_file_read_as_buf(const char *path);
|
||||
IW_EXPORT char* iwu_file_read_as_buf(const char *path);
|
||||
|
||||
IW_EXPORT char* iwu_file_read_as_buf_len(const char *path, size_t *out_len);
|
||||
|
||||
/**
|
||||
* @brief Create X31 hash value.
|
||||
|
||||
+124
-12
@@ -4,7 +4,6 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <stdarg.h>
|
||||
#include <errno.h>
|
||||
|
||||
// Default IWXSTR initial size
|
||||
@@ -45,15 +44,46 @@ IWXSTR* iwxstr_new(void) {
|
||||
return iwxstr_new2(IWXSTR_AUNIT);
|
||||
}
|
||||
|
||||
IWXSTR* iwxstr_wrap(const char *buf, size_t size) {
|
||||
IWXSTR *xstr = iwxstr_new2(size + 1);
|
||||
IWXSTR* iwxstr_new_clone(const IWXSTR *xstr) {
|
||||
IWXSTR *ret = malloc(sizeof(*ret));
|
||||
if (!ret) {
|
||||
return 0;
|
||||
}
|
||||
ret->user_data = 0;
|
||||
ret->user_data_free_fn = 0;
|
||||
ret->size = xstr->size;
|
||||
ret->asize = xstr->asize;
|
||||
ret->ptr = malloc(xstr->asize);
|
||||
if (!ret->ptr) {
|
||||
free(ret);
|
||||
return 0;
|
||||
}
|
||||
if (xstr->size) {
|
||||
memcpy(ret->ptr, xstr->ptr, xstr->size);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
IWXSTR* iwxstr_wrap(char *buf, size_t size, size_t asize) {
|
||||
IWXSTR *xstr = malloc(sizeof(*xstr));
|
||||
if (!xstr) {
|
||||
return 0;
|
||||
}
|
||||
if (iwxstr_cat(xstr, buf, size)) {
|
||||
iwxstr_destroy(xstr);
|
||||
return 0;
|
||||
xstr->user_data = 0;
|
||||
xstr->user_data_free_fn = 0;
|
||||
xstr->size = size;
|
||||
xstr->asize = asize;
|
||||
xstr->ptr = buf;
|
||||
|
||||
if (size >= asize) {
|
||||
xstr->ptr = realloc(buf, size + 1);
|
||||
if (!xstr->ptr) {
|
||||
free(xstr);
|
||||
return 0;
|
||||
}
|
||||
xstr->asize = size + 1;
|
||||
}
|
||||
xstr->ptr[size] = '\0';
|
||||
return xstr;
|
||||
}
|
||||
|
||||
@@ -68,14 +98,16 @@ void iwxstr_destroy(IWXSTR *xstr) {
|
||||
free(xstr);
|
||||
}
|
||||
|
||||
void iwxstr_destroy_keep_ptr(IWXSTR *xstr) {
|
||||
char* iwxstr_destroy_keep_ptr(IWXSTR *xstr) {
|
||||
if (!xstr) {
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
char *ptr = xstr->ptr;
|
||||
if (xstr->user_data_free_fn) {
|
||||
xstr->user_data_free_fn(xstr->user_data);
|
||||
}
|
||||
free(xstr);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void iwxstr_clear(IWXSTR *xstr) {
|
||||
@@ -95,7 +127,7 @@ iwrc iwxstr_cat(IWXSTR *xstr, const void *buf, size_t size) {
|
||||
}
|
||||
char *ptr = realloc(xstr->ptr, xstr->asize);
|
||||
if (!ptr) {
|
||||
return IW_ERROR_ERRNO;
|
||||
return IW_ERROR_ALLOC;
|
||||
}
|
||||
xstr->ptr = ptr;
|
||||
}
|
||||
@@ -116,7 +148,7 @@ iwrc iwxstr_set_size(IWXSTR *xstr, size_t size) {
|
||||
}
|
||||
char *ptr = realloc(xstr->ptr, xstr->asize);
|
||||
if (!ptr) {
|
||||
return IW_ERROR_ERRNO;
|
||||
return IW_ERROR_ALLOC;
|
||||
}
|
||||
xstr->ptr = ptr;
|
||||
}
|
||||
@@ -139,7 +171,7 @@ iwrc iwxstr_unshift(IWXSTR *xstr, const void *buf, size_t size) {
|
||||
}
|
||||
char *ptr = realloc(xstr->ptr, xstr->asize);
|
||||
if (!ptr) {
|
||||
return IW_ERROR_ERRNO;
|
||||
return IW_ERROR_ALLOC;
|
||||
}
|
||||
xstr->ptr = ptr;
|
||||
}
|
||||
@@ -178,7 +210,34 @@ void iwxstr_pop(IWXSTR *xstr, size_t pop_size) {
|
||||
xstr->ptr[xstr->size] = '\0';
|
||||
}
|
||||
|
||||
static iwrc iwxstr_vaprintf(IWXSTR *xstr, const char *format, va_list va) {
|
||||
iwrc iwxstr_insert(IWXSTR *xstr, size_t pos, const void *buf, size_t size) {
|
||||
if (pos > xstr->size) {
|
||||
return IW_ERROR_OUT_OF_BOUNDS;
|
||||
}
|
||||
if (size == 0) {
|
||||
return 0;
|
||||
}
|
||||
size_t nsize = xstr->size + size + 1;
|
||||
if (xstr->asize < nsize) {
|
||||
while (xstr->asize < nsize) {
|
||||
xstr->asize <<= 1;
|
||||
if (xstr->asize < nsize) {
|
||||
xstr->asize = nsize;
|
||||
}
|
||||
}
|
||||
char *ptr = realloc(xstr->ptr, xstr->asize);
|
||||
if (!ptr) {
|
||||
return IW_ERROR_ALLOC;
|
||||
}
|
||||
xstr->ptr = ptr;
|
||||
}
|
||||
memmove(xstr->ptr + pos + size, xstr->ptr + pos, xstr->size - pos + 1 /* \0 */);
|
||||
memcpy(xstr->ptr + pos, buf, size);
|
||||
xstr->size += size;
|
||||
return IW_OK;
|
||||
}
|
||||
|
||||
iwrc iwxstr_insert_vaprintf(IWXSTR *xstr, size_t pos, const char *format, va_list va) {
|
||||
iwrc rc = 0;
|
||||
char buf[1024];
|
||||
va_list cva;
|
||||
@@ -188,6 +247,43 @@ static iwrc iwxstr_vaprintf(IWXSTR *xstr, const char *format, va_list va) {
|
||||
if (len >= sizeof(buf)) {
|
||||
RCA(wp = malloc(len + 1), finish);
|
||||
len = vsnprintf(wp, len + 1, format, cva);
|
||||
if (len < 0) {
|
||||
rc = IW_ERROR_FAIL;
|
||||
goto finish;
|
||||
}
|
||||
}
|
||||
rc = iwxstr_insert(xstr, pos, wp, len);
|
||||
|
||||
finish:
|
||||
va_end(cva);
|
||||
if (wp != buf) {
|
||||
free(wp);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
iwrc iwxstr_insert_printf(IWXSTR *xstr, size_t pos, const char *format, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
iwrc rc = iwxstr_insert_vaprintf(xstr, pos, format, ap);
|
||||
va_end(ap);
|
||||
return rc;
|
||||
}
|
||||
|
||||
iwrc iwxstr_vaprintf(IWXSTR *xstr, const char *format, va_list va) {
|
||||
iwrc rc = 0;
|
||||
char buf[1024];
|
||||
va_list cva;
|
||||
va_copy(cva, va);
|
||||
char *wp = buf;
|
||||
int len = vsnprintf(wp, sizeof(buf), format, va);
|
||||
if (len >= sizeof(buf)) {
|
||||
RCA(wp = malloc(len + 1), finish);
|
||||
len = vsnprintf(wp, len + 1, format, cva);
|
||||
if (len < 0) {
|
||||
rc = IW_ERROR_FAIL;
|
||||
goto finish;
|
||||
}
|
||||
}
|
||||
rc = iwxstr_cat(xstr, wp, len);
|
||||
|
||||
@@ -207,6 +303,22 @@ iwrc iwxstr_printf(IWXSTR *xstr, const char *format, ...) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
IWXSTR* iwxstr_new_printf(const char *format, ...) {
|
||||
IWXSTR *xstr = iwxstr_new();
|
||||
if (!xstr) {
|
||||
return 0;
|
||||
}
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
iwrc rc = iwxstr_vaprintf(xstr, format, ap);
|
||||
va_end(ap);
|
||||
if (rc) {
|
||||
iwxstr_destroy(xstr);
|
||||
return 0;
|
||||
}
|
||||
return xstr;
|
||||
}
|
||||
|
||||
char* iwxstr_ptr(IWXSTR *xstr) {
|
||||
return xstr->ptr;
|
||||
}
|
||||
|
||||
+16
-2
@@ -30,6 +30,8 @@
|
||||
|
||||
#include "basedefs.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
IW_EXTERN_C_START
|
||||
|
||||
typedef struct _IWXSTR IWXSTR;
|
||||
@@ -38,11 +40,15 @@ IW_EXPORT IW_ALLOC IWXSTR* iwxstr_new(void);
|
||||
|
||||
IW_EXPORT IWXSTR* iwxstr_new2(size_t siz);
|
||||
|
||||
IW_EXPORT IWXSTR* iwxstr_wrap(const char *buf, size_t size);
|
||||
IW_EXPORT IWXSTR* iwxstr_new_printf(const char *format, ...) __attribute__ ((format(__printf__, 1, 2)));
|
||||
|
||||
IW_EXPORT IWXSTR* iwxstr_new_clone(const IWXSTR *xstr);
|
||||
|
||||
IW_EXPORT IWXSTR* iwxstr_wrap(char *buf, size_t size, size_t asize);
|
||||
|
||||
IW_EXPORT void iwxstr_destroy(IWXSTR *xstr);
|
||||
|
||||
IW_EXPORT void iwxstr_destroy_keep_ptr(IWXSTR *xstr);
|
||||
IW_EXPORT IW_ALLOC char* iwxstr_destroy_keep_ptr(IWXSTR *xstr);
|
||||
|
||||
IW_EXPORT iwrc iwxstr_cat(IWXSTR *xstr, const void *buf, size_t size);
|
||||
|
||||
@@ -50,12 +56,20 @@ IW_EXPORT iwrc iwxstr_cat2(IWXSTR *xstr, const char *buf);
|
||||
|
||||
IW_EXPORT iwrc iwxstr_unshift(IWXSTR *xstr, const void *buf, size_t size);
|
||||
|
||||
IW_EXPORT iwrc iwxstr_vaprintf(IWXSTR *xstr, const char *format, va_list va);
|
||||
|
||||
IW_EXPORT iwrc iwxstr_printf(IWXSTR *xstr, const char *format, ...) __attribute__((format(__printf__, 2, 3)));
|
||||
|
||||
IW_EXPORT void iwxstr_shift(IWXSTR *xstr, size_t shift_size);
|
||||
|
||||
IW_EXPORT void iwxstr_pop(IWXSTR *xstr, size_t pop_size);
|
||||
|
||||
IW_EXPORT iwrc iwxstr_insert(IWXSTR *xstr, size_t pos, const void *buf, size_t size);
|
||||
|
||||
IW_EXPORT iwrc iwxstr_insert_vaprintf(IWXSTR *xstr, size_t pos, const char *format, va_list va);
|
||||
|
||||
IW_EXPORT iwrc iwxstr_insert_printf(IWXSTR *xstr, size_t pos, const char *format, ...) __attribute__((format(__printf__, 3, 4)));
|
||||
|
||||
IW_EXPORT char* iwxstr_ptr(IWXSTR *xstr);
|
||||
|
||||
IW_EXPORT iwrc iwxstr_set_size(IWXSTR *xstr, size_t size);
|
||||
|
||||
@@ -1,629 +0,0 @@
|
||||
// -V::1003
|
||||
|
||||
/* The MIT License
|
||||
|
||||
Copyright (c) 2008, 2009, 2011 by Attractive Chaos <attractor@live.co.uk>
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
/*
|
||||
An example:
|
||||
|
||||
#include "khash.h"
|
||||
KHASH_MAP_INIT_INT(32, char)
|
||||
int main() {
|
||||
int ret, is_missing;
|
||||
khiter_t k;
|
||||
khash_t(32) *h = kh_init(32);
|
||||
k = kh_put(32, h, 5, &ret);
|
||||
kh_value(h, k) = 10;
|
||||
k = kh_get(32, h, 10);
|
||||
is_missing = (k == kh_end(h));
|
||||
k = kh_get(32, h, 5);
|
||||
kh_del(32, h, k);
|
||||
for (k = kh_begin(h); k != kh_end(h); ++k)
|
||||
if (kh_exist(h, k)) kh_value(h, k) = 1;
|
||||
kh_destroy(32, h);
|
||||
return 0;
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
2013-05-02 (0.2.8):
|
||||
|
||||
* Use quadratic probing. When the capacity is power of 2, stepping function
|
||||
i*(i+1)/2 guarantees to traverse each bucket. It is better than double
|
||||
hashing on cache performance and is more robust than linear probing.
|
||||
|
||||
In theory, double hashing should be more robust than quadratic probing.
|
||||
However, my implementation is probably not for large hash tables, because
|
||||
the second hash function is closely tied to the first hash function,
|
||||
which reduce the effectiveness of double hashing.
|
||||
|
||||
Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php
|
||||
|
||||
2011-12-29 (0.2.7):
|
||||
|
||||
* Minor code clean up; no actual effect.
|
||||
|
||||
2011-09-16 (0.2.6):
|
||||
|
||||
* The capacity is a power of 2. This seems to dramatically improve the
|
||||
speed for simple keys. Thank Zilong Tan for the suggestion. Reference:
|
||||
|
||||
- http://code.google.com/p/ulib/
|
||||
- http://nothings.org/computer/judy/
|
||||
|
||||
* Allow to optionally use linear probing which usually has better
|
||||
performance for random input. Double hashing is still the default as it
|
||||
is more robust to certain non-random input.
|
||||
|
||||
* Added Wang's integer hash function (not used by default). This hash
|
||||
function is more robust to certain non-random input.
|
||||
|
||||
2011-02-14 (0.2.5):
|
||||
|
||||
* Allow to declare global functions.
|
||||
|
||||
2009-09-26 (0.2.4):
|
||||
|
||||
* Improve portability
|
||||
|
||||
2008-09-19 (0.2.3):
|
||||
|
||||
* Corrected the example
|
||||
* Improved interfaces
|
||||
|
||||
2008-09-11 (0.2.2):
|
||||
|
||||
* Improved speed a little in kh_put()
|
||||
|
||||
2008-09-10 (0.2.1):
|
||||
|
||||
* Added kh_clear()
|
||||
* Fixed a compiling error
|
||||
|
||||
2008-09-02 (0.2.0):
|
||||
|
||||
* Changed to token concatenation which increases flexibility.
|
||||
|
||||
2008-08-31 (0.1.2):
|
||||
|
||||
* Fixed a bug in kh_get(), which has not been tested previously.
|
||||
|
||||
2008-08-31 (0.1.1):
|
||||
|
||||
* Added destructor
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __AC_KHASH_H
|
||||
#define __AC_KHASH_H
|
||||
|
||||
/*!
|
||||
@header
|
||||
|
||||
Generic hash table library.
|
||||
*/
|
||||
|
||||
#define AC_VERSION_KHASH_H "0.2.8"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
|
||||
/* compiler specific configuration */
|
||||
|
||||
#if UINT_MAX == 0xffffffffu
|
||||
typedef unsigned int khint32_t;
|
||||
#elif ULONG_MAX == 0xffffffffu
|
||||
typedef unsigned long khint32_t;
|
||||
#endif
|
||||
|
||||
#if ULONG_MAX == ULLONG_MAX
|
||||
typedef unsigned long khint64_t;
|
||||
#else
|
||||
typedef unsigned long long khint64_t;
|
||||
#endif
|
||||
|
||||
#ifndef kh_inline
|
||||
#ifdef _MSC_VER
|
||||
#define kh_inline __inline
|
||||
#else
|
||||
#define kh_inline inline
|
||||
#endif
|
||||
#endif /* kh_inline */
|
||||
|
||||
#ifndef klib_unused
|
||||
#if (defined __clang__ && __clang_major__ >= 3) || (defined __GNUC__ && __GNUC__ >= 3)
|
||||
#define klib_unused __attribute__ ((__unused__))
|
||||
#else
|
||||
#define klib_unused
|
||||
#endif
|
||||
#endif /* klib_unused */
|
||||
|
||||
typedef khint32_t khint_t;
|
||||
typedef khint_t khiter_t;
|
||||
|
||||
#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2)
|
||||
#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1)
|
||||
#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3)
|
||||
#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1)))
|
||||
#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1)))
|
||||
#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1)))
|
||||
#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1))
|
||||
|
||||
#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4)
|
||||
|
||||
#ifndef kroundup32
|
||||
#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x))
|
||||
#endif
|
||||
|
||||
#ifndef kcalloc
|
||||
#define kcalloc(N,Z) calloc(N,Z)
|
||||
#endif
|
||||
#ifndef kmalloc
|
||||
#define kmalloc(Z) malloc(Z)
|
||||
#endif
|
||||
#ifndef krealloc
|
||||
#define krealloc(P,Z) realloc(P,Z)
|
||||
#endif
|
||||
#ifndef kfree
|
||||
#define kfree(P) free(P)
|
||||
#endif
|
||||
|
||||
static const double __ac_HASH_UPPER = 0.77;
|
||||
|
||||
#define __KHASH_TYPE(name, khkey_t, khval_t) \
|
||||
typedef struct kh_##name##_s { \
|
||||
khint_t n_buckets, size, n_occupied, upper_bound; \
|
||||
khint32_t *flags; \
|
||||
khkey_t *keys; \
|
||||
khval_t *vals; \
|
||||
} kh_##name##_t;
|
||||
|
||||
#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \
|
||||
extern kh_##name##_t *kh_init_##name(void); \
|
||||
extern void kh_destroy_##name(kh_##name##_t *h); \
|
||||
extern void kh_clear_##name(kh_##name##_t *h); \
|
||||
extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \
|
||||
extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \
|
||||
extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \
|
||||
extern void kh_del_##name(kh_##name##_t *h, khint_t x);
|
||||
|
||||
#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
|
||||
SCOPE kh_##name##_t *kh_init_##name(void) { \
|
||||
return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \
|
||||
} \
|
||||
SCOPE void kh_destroy_##name(kh_##name##_t *h) \
|
||||
{ \
|
||||
if (h) { \
|
||||
kfree((void *)h->keys); kfree(h->flags); \
|
||||
kfree((void *)h->vals); \
|
||||
kfree(h); \
|
||||
} \
|
||||
} \
|
||||
SCOPE void kh_clear_##name(kh_##name##_t *h) \
|
||||
{ \
|
||||
if (h && h->flags) { \
|
||||
memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \
|
||||
h->size = h->n_occupied = 0; \
|
||||
} \
|
||||
} \
|
||||
SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \
|
||||
{ \
|
||||
if (h->n_buckets) { \
|
||||
khint_t k, i, last, mask, step = 0; \
|
||||
mask = h->n_buckets - 1; \
|
||||
k = __hash_func(key); i = k & mask; \
|
||||
last = i; \
|
||||
while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \
|
||||
i = (i + (++step)) & mask; \
|
||||
if (i == last) return h->n_buckets; \
|
||||
} \
|
||||
return __ac_iseither(h->flags, i)? h->n_buckets : i; \
|
||||
} else return 0; \
|
||||
} \
|
||||
SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \
|
||||
{ /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \
|
||||
khint32_t *new_flags = 0; \
|
||||
khint_t j = 1; \
|
||||
{ \
|
||||
kroundup32(new_n_buckets); \
|
||||
if (new_n_buckets < 4) new_n_buckets = 4; \
|
||||
if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \
|
||||
else { /* hash table size to be changed (shrink or expand); rehash */ \
|
||||
new_flags = (khint32_t*)kmalloc(__ac_fsize(new_n_buckets) * sizeof(khint32_t)); \
|
||||
if (!new_flags) return -1; \
|
||||
memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \
|
||||
if (h->n_buckets < new_n_buckets) { /* expand */ \
|
||||
khkey_t *new_keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \
|
||||
if (!new_keys) { kfree(new_flags); return -1; } \
|
||||
h->keys = new_keys; \
|
||||
if (kh_is_map) { \
|
||||
khval_t *new_vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \
|
||||
if (!new_vals) { kfree(new_flags); return -1; } \
|
||||
h->vals = new_vals; \
|
||||
} \
|
||||
} /* otherwise shrink */ \
|
||||
} \
|
||||
} \
|
||||
if (j) { /* rehashing is needed */ \
|
||||
for (j = 0; j != h->n_buckets; ++j) { \
|
||||
if (__ac_iseither(h->flags, j) == 0) { \
|
||||
khkey_t key = h->keys[j]; \
|
||||
khval_t val; \
|
||||
khint_t new_mask; \
|
||||
new_mask = new_n_buckets - 1; \
|
||||
if (kh_is_map) val = h->vals[j]; \
|
||||
__ac_set_isdel_true(h->flags, j); \
|
||||
while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \
|
||||
khint_t k, i, step = 0; \
|
||||
k = __hash_func(key); \
|
||||
i = k & new_mask; \
|
||||
while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \
|
||||
__ac_set_isempty_false(new_flags, i); \
|
||||
if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \
|
||||
{ khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \
|
||||
if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \
|
||||
__ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \
|
||||
} else { /* write the element and jump out of the loop */ \
|
||||
h->keys[i] = key; \
|
||||
if (kh_is_map) h->vals[i] = val; \
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \
|
||||
h->keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \
|
||||
if (kh_is_map) h->vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \
|
||||
} \
|
||||
kfree(h->flags); /* free the working space */ \
|
||||
h->flags = new_flags; \
|
||||
h->n_buckets = new_n_buckets; \
|
||||
h->n_occupied = h->size; \
|
||||
h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \
|
||||
} \
|
||||
return 0; \
|
||||
} \
|
||||
SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \
|
||||
{ \
|
||||
khint_t x; \
|
||||
if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \
|
||||
if (h->n_buckets > (h->size<<1)) { \
|
||||
if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \
|
||||
*ret = -1; return h->n_buckets; \
|
||||
} \
|
||||
} else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \
|
||||
*ret = -1; return h->n_buckets; \
|
||||
} \
|
||||
} /* TODO: to implement automatically shrinking; resize() already support shrinking */ \
|
||||
{ \
|
||||
khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \
|
||||
x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \
|
||||
if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \
|
||||
else { \
|
||||
last = i; \
|
||||
while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \
|
||||
if (__ac_isdel(h->flags, i)) site = i; \
|
||||
i = (i + (++step)) & mask; \
|
||||
if (i == last) { x = site; break; } \
|
||||
} \
|
||||
if (x == h->n_buckets) { \
|
||||
if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \
|
||||
else x = i; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
if (__ac_isempty(h->flags, x)) { /* not present at all */ \
|
||||
h->keys[x] = key; \
|
||||
__ac_set_isboth_false(h->flags, x); \
|
||||
++h->size; ++h->n_occupied; \
|
||||
*ret = 1; \
|
||||
} else if (__ac_isdel(h->flags, x)) { /* deleted */ \
|
||||
h->keys[x] = key; \
|
||||
__ac_set_isboth_false(h->flags, x); \
|
||||
++h->size; \
|
||||
*ret = 2; \
|
||||
} else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \
|
||||
return x; \
|
||||
} \
|
||||
SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \
|
||||
{ \
|
||||
if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \
|
||||
__ac_set_isdel_true(h->flags, x); \
|
||||
--h->size; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define KHASH_DECLARE(name, khkey_t, khval_t) \
|
||||
__KHASH_TYPE(name, khkey_t, khval_t) \
|
||||
__KHASH_PROTOTYPES(name, khkey_t, khval_t)
|
||||
|
||||
#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
|
||||
__KHASH_TYPE(name, khkey_t, khval_t) \
|
||||
__KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
|
||||
|
||||
#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
|
||||
KHASH_INIT2(name, static kh_inline klib_unused, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
|
||||
|
||||
/* --- BEGIN OF HASH FUNCTIONS --- */
|
||||
|
||||
/*! @function
|
||||
@abstract Integer hash function
|
||||
@param key The integer [khint32_t]
|
||||
@return The hash value [khint_t]
|
||||
*/
|
||||
#define kh_int_hash_func(key) (khint32_t)(key)
|
||||
/*! @function
|
||||
@abstract Integer comparison function
|
||||
*/
|
||||
#define kh_int_hash_equal(a, b) ((a) == (b))
|
||||
/*! @function
|
||||
@abstract 64-bit integer hash function
|
||||
@param key The integer [khint64_t]
|
||||
@return The hash value [khint_t]
|
||||
*/
|
||||
#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11)
|
||||
/*! @function
|
||||
@abstract 64-bit integer comparison function
|
||||
*/
|
||||
#define kh_int64_hash_equal(a, b) ((a) == (b))
|
||||
/*! @function
|
||||
@abstract const char* hash function
|
||||
@param s Pointer to a null terminated string
|
||||
@return The hash value
|
||||
*/
|
||||
static kh_inline khint_t __ac_X31_hash_string(const char *s)
|
||||
{
|
||||
khint_t h = (khint_t)*s;
|
||||
if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s;
|
||||
return h;
|
||||
}
|
||||
/*! @function
|
||||
@abstract Another interface to const char* hash function
|
||||
@param key Pointer to a null terminated string [const char*]
|
||||
@return The hash value [khint_t]
|
||||
*/
|
||||
#define kh_str_hash_func(key) __ac_X31_hash_string(key)
|
||||
/*! @function
|
||||
@abstract Const char* comparison function
|
||||
*/
|
||||
#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0)
|
||||
|
||||
static kh_inline khint_t __ac_Wang_hash(khint_t key)
|
||||
{
|
||||
key += ~(key << 15);
|
||||
key ^= (key >> 10);
|
||||
key += (key << 3);
|
||||
key ^= (key >> 6);
|
||||
key += ~(key << 11);
|
||||
key ^= (key >> 16);
|
||||
return key;
|
||||
}
|
||||
#define kh_int_hash_func2(key) __ac_Wang_hash((khint_t)key)
|
||||
|
||||
/* --- END OF HASH FUNCTIONS --- */
|
||||
|
||||
/* Other convenient macros... */
|
||||
|
||||
/*!
|
||||
@abstract Type of the hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
*/
|
||||
#define khash_t(name) kh_##name##_t
|
||||
|
||||
/*! @function
|
||||
@abstract Initiate a hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
@return Pointer to the hash table [khash_t(name)*]
|
||||
*/
|
||||
#define kh_init(name) kh_init_##name()
|
||||
|
||||
/*! @function
|
||||
@abstract Destroy a hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
*/
|
||||
#define kh_destroy(name, h) kh_destroy_##name(h)
|
||||
|
||||
/*! @function
|
||||
@abstract Reset a hash table without deallocating memory.
|
||||
@param name Name of the hash table [symbol]
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
*/
|
||||
#define kh_clear(name, h) kh_clear_##name(h)
|
||||
|
||||
/*! @function
|
||||
@abstract Resize a hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param s New size [khint_t]
|
||||
*/
|
||||
#define kh_resize(name, h, s) kh_resize_##name(h, s)
|
||||
|
||||
/*! @function
|
||||
@abstract Insert a key to the hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param k Key [type of keys]
|
||||
@param r Extra return code: -1 if the operation failed;
|
||||
0 if the key is present in the hash table;
|
||||
1 if the bucket is empty (never used); 2 if the element in
|
||||
the bucket has been deleted [int*]
|
||||
@return Iterator to the inserted element [khint_t]
|
||||
*/
|
||||
#define kh_put(name, h, k, r) kh_put_##name(h, k, r)
|
||||
|
||||
/*! @function
|
||||
@abstract Retrieve a key from the hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param k Key [type of keys]
|
||||
@return Iterator to the found element, or kh_end(h) if the element is absent [khint_t]
|
||||
*/
|
||||
#define kh_get(name, h, k) kh_get_##name(h, k)
|
||||
|
||||
/*! @function
|
||||
@abstract Remove a key from the hash table.
|
||||
@param name Name of the hash table [symbol]
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param k Iterator to the element to be deleted [khint_t]
|
||||
*/
|
||||
#define kh_del(name, h, k) kh_del_##name(h, k)
|
||||
|
||||
/*! @function
|
||||
@abstract Test whether a bucket contains data.
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param x Iterator to the bucket [khint_t]
|
||||
@return 1 if containing data; 0 otherwise [int]
|
||||
*/
|
||||
#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x)))
|
||||
|
||||
/*! @function
|
||||
@abstract Get key given an iterator
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param x Iterator to the bucket [khint_t]
|
||||
@return Key [type of keys]
|
||||
*/
|
||||
#define kh_key(h, x) ((h)->keys[x])
|
||||
|
||||
/*! @function
|
||||
@abstract Get value given an iterator
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param x Iterator to the bucket [khint_t]
|
||||
@return Value [type of values]
|
||||
@discussion For hash sets, calling this results in segfault.
|
||||
*/
|
||||
#define kh_val(h, x) ((h)->vals[x])
|
||||
|
||||
/*! @function
|
||||
@abstract Alias of kh_val()
|
||||
*/
|
||||
#define kh_value(h, x) ((h)->vals[x])
|
||||
|
||||
/*! @function
|
||||
@abstract Get the start iterator
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@return The start iterator [khint_t]
|
||||
*/
|
||||
#define kh_begin(h) (khint_t)(0)
|
||||
|
||||
/*! @function
|
||||
@abstract Get the end iterator
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@return The end iterator [khint_t]
|
||||
*/
|
||||
#define kh_end(h) ((h)->n_buckets)
|
||||
|
||||
/*! @function
|
||||
@abstract Get the number of elements in the hash table
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@return Number of elements in the hash table [khint_t]
|
||||
*/
|
||||
#define kh_size(h) ((h)->size)
|
||||
|
||||
/*! @function
|
||||
@abstract Get the number of buckets in the hash table
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@return Number of buckets in the hash table [khint_t]
|
||||
*/
|
||||
#define kh_n_buckets(h) ((h)->n_buckets)
|
||||
|
||||
/*! @function
|
||||
@abstract Iterate over the entries in the hash table
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param kvar Variable to which key will be assigned
|
||||
@param vvar Variable to which value will be assigned
|
||||
@param code Block of code to execute
|
||||
*/
|
||||
#define kh_foreach(h, kvar, vvar, code) { khint_t __i; \
|
||||
for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \
|
||||
if (!kh_exist(h,__i)) continue; \
|
||||
(kvar) = kh_key(h,__i); \
|
||||
(vvar) = kh_val(h,__i); \
|
||||
code; \
|
||||
} }
|
||||
|
||||
/*! @function
|
||||
@abstract Iterate over the values in the hash table
|
||||
@param h Pointer to the hash table [khash_t(name)*]
|
||||
@param vvar Variable to which value will be assigned
|
||||
@param code Block of code to execute
|
||||
*/
|
||||
#define kh_foreach_value(h, vvar, code) { khint_t __i; \
|
||||
for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \
|
||||
if (!kh_exist(h,__i)) continue; \
|
||||
(vvar) = kh_val(h,__i); \
|
||||
code; \
|
||||
} }
|
||||
|
||||
/* More convenient interfaces */
|
||||
|
||||
/*! @function
|
||||
@abstract Instantiate a hash set containing integer keys
|
||||
@param name Name of the hash table [symbol]
|
||||
*/
|
||||
#define KHASH_SET_INIT_INT(name) \
|
||||
KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal)
|
||||
|
||||
/*! @function
|
||||
@abstract Instantiate a hash map containing integer keys
|
||||
@param name Name of the hash table [symbol]
|
||||
@param khval_t Type of values [type]
|
||||
*/
|
||||
#define KHASH_MAP_INIT_INT(name, khval_t) \
|
||||
KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal)
|
||||
|
||||
/*! @function
|
||||
@abstract Instantiate a hash set containing 64-bit integer keys
|
||||
@param name Name of the hash table [symbol]
|
||||
*/
|
||||
#define KHASH_SET_INIT_INT64(name) \
|
||||
KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal)
|
||||
|
||||
/*! @function
|
||||
@abstract Instantiate a hash map containing 64-bit integer keys
|
||||
@param name Name of the hash table [symbol]
|
||||
@param khval_t Type of values [type]
|
||||
*/
|
||||
#define KHASH_MAP_INIT_INT64(name, khval_t) \
|
||||
KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal)
|
||||
|
||||
typedef const char *kh_cstr_t;
|
||||
/*! @function
|
||||
@abstract Instantiate a hash map containing const char* keys
|
||||
@param name Name of the hash table [symbol]
|
||||
*/
|
||||
#define KHASH_SET_INIT_STR(name) \
|
||||
KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal)
|
||||
|
||||
/*! @function
|
||||
@abstract Instantiate a hash map containing const char* keys
|
||||
@param name Name of the hash table [symbol]
|
||||
@param khval_t Type of values [type]
|
||||
*/
|
||||
#define KHASH_MAP_INIT_STR(name, khval_t) \
|
||||
KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal)
|
||||
|
||||
#endif /* __AC_KHASH_H */
|
||||
@@ -62,7 +62,7 @@ static int mti = N + 1; /* mti==N+1 means mt[N] is not initialized */
|
||||
static pthread_spinlock_t lock;
|
||||
static volatile int mt_initialized;
|
||||
|
||||
void init_mt19937ar() {
|
||||
void init_mt19937ar(void) {
|
||||
if (!__sync_bool_compare_and_swap(&mt_initialized, 0, 1)) {
|
||||
return; // initialized already
|
||||
}
|
||||
@@ -71,13 +71,13 @@ void init_mt19937ar() {
|
||||
|
||||
__attribute__((constructor))
|
||||
|
||||
void lock_constructor() {
|
||||
void lock_constructor(void) {
|
||||
init_mt19937ar();
|
||||
}
|
||||
|
||||
__attribute__((destructor))
|
||||
|
||||
void lock_destructor() {
|
||||
void lock_destructor(void) {
|
||||
if (!__sync_bool_compare_and_swap(&mt_initialized, 1, 0)) {
|
||||
return; // initialized already
|
||||
}
|
||||
|
||||
@@ -4,13 +4,11 @@ include_directories(${CUNIT_INCLUDE_DIRS})
|
||||
set(TEST_DATA_DIR ${CMAKE_CURRENT_BINARY_DIR})
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${TEST_DATA_DIR})
|
||||
|
||||
foreach(TN IN ITEMS iwarr_test1
|
||||
iwutils_test1
|
||||
iwhmap_test1
|
||||
)
|
||||
add_executable(${TN} ${TN}.c)
|
||||
set_target_properties(${TN} PROPERTIES
|
||||
COMPILE_FLAGS "-DIW_STATIC")
|
||||
add_test(NAME ${TN} WORKING_DIRECTORY ${TEST_DATA_DIR}
|
||||
COMMAND ${TEST_TOOL_CMD} $<TARGET_FILE:${TN}>)
|
||||
foreach(TN IN ITEMS iwarr_test1 iwutils_test1 iwhmap_test1)
|
||||
add_executable(${TN} ${TN}.c)
|
||||
set_target_properties(${TN} PROPERTIES COMPILE_FLAGS "-DIW_STATIC")
|
||||
add_test(
|
||||
NAME ${TN}
|
||||
WORKING_DIRECTORY ${TEST_DATA_DIR}
|
||||
COMMAND ${TEST_TOOL_CMD} $<TARGET_FILE:${TN}>)
|
||||
endforeach()
|
||||
|
||||
@@ -24,7 +24,6 @@ void murmur3_x86_128(const void *key, const size_t len, uint32_t seed, void *out
|
||||
void murmur3_x64_128(const void *key, const size_t len, const uint32_t seed, void *out);
|
||||
|
||||
static void test_murmur_hash(void) {
|
||||
|
||||
#define TESTHASH(arch, nbytes, seed, str, expected) { \
|
||||
char *input = str; \
|
||||
uint32_t hash[4]; \
|
||||
@@ -87,7 +86,57 @@ static void test_basic_crud_str(void) {
|
||||
}
|
||||
CU_ASSERT_EQUAL(iwhmap_count(hm), 3333);
|
||||
|
||||
// todo: finish tests
|
||||
// TODO: finish tests
|
||||
iwhmap_destroy(hm);
|
||||
}
|
||||
|
||||
static void test_lru1(void) {
|
||||
IWHMAP *hm = iwhmap_create_u32(0);
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(hm);
|
||||
|
||||
// Init LRU mode max 2 records in map
|
||||
iwhmap_lru_init(hm, iwhmap_lru_eviction_max_count, (void*) (uintptr_t) 2UL);
|
||||
|
||||
iwrc rc = iwhmap_put_u32(hm, 1, (void*) 1L);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
long val = (intptr_t) iwhmap_get_u64(hm, 1);
|
||||
CU_ASSERT_EQUAL(val, 1L);
|
||||
|
||||
rc -= iwhmap_put_u32(hm, 2, (void*) 2L);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
val = (intptr_t) iwhmap_get_u64(hm, 1);
|
||||
CU_ASSERT_EQUAL(val, 1L);
|
||||
val = (intptr_t) iwhmap_get_u64(hm, 2);
|
||||
CU_ASSERT_EQUAL(val, 2L);
|
||||
|
||||
rc = iwhmap_put_u32(hm, 3, (void*) 3L);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
val = (intptr_t) iwhmap_get_u64(hm, 1);
|
||||
CU_ASSERT_EQUAL(val, 0L);
|
||||
val = (intptr_t) iwhmap_get_u64(hm, 3);
|
||||
CU_ASSERT_EQUAL(val, 3L);
|
||||
val = (intptr_t) iwhmap_get_u64(hm, 2);
|
||||
CU_ASSERT_EQUAL(val, 2L);
|
||||
|
||||
rc = iwhmap_put_u32(hm, 4, (void*) 4L);
|
||||
val = (intptr_t) iwhmap_get_u64(hm, 3);
|
||||
CU_ASSERT_EQUAL(val, 0);
|
||||
CU_ASSERT_EQUAL(iwhmap_count(hm), 2);
|
||||
|
||||
iwhmap_destroy(hm);
|
||||
}
|
||||
|
||||
static void test_lru2(void) {
|
||||
iwrc rc = 0;
|
||||
IWHMAP *hm = iwhmap_create_u32(0);
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(hm);
|
||||
iwhmap_lru_init(hm, iwhmap_lru_eviction_max_count, (void*) (uintptr_t) 1024UL);
|
||||
for (int i = 0; i < 2048; ++i) {
|
||||
rc = iwhmap_put_u32(hm, i + 1, (void*) (intptr_t) i);
|
||||
CU_ASSERT_EQUAL_FATAL(rc, 0);
|
||||
}
|
||||
uint32_t val = iwhmap_count(hm);
|
||||
CU_ASSERT_EQUAL(val, 1024);
|
||||
|
||||
iwhmap_destroy(hm);
|
||||
}
|
||||
@@ -110,7 +159,9 @@ int main() {
|
||||
|
||||
/* Add the tests to the suite */
|
||||
if ( (NULL == CU_add_test(pSuite, "test_murmur_hash", test_murmur_hash))
|
||||
|| (NULL == CU_add_test(pSuite, "test_basic_crud_str", test_basic_crud_str))) {
|
||||
|| (NULL == CU_add_test(pSuite, "test_basic_crud_str", test_basic_crud_str))
|
||||
|| (NULL == CU_add_test(pSuite, "test_lru1", test_lru1))
|
||||
|| (NULL == CU_add_test(pSuite, "test_lru2", test_lru2))) {
|
||||
CU_cleanup_registry();
|
||||
return CU_get_error();
|
||||
}
|
||||
|
||||
@@ -4,12 +4,13 @@
|
||||
#include "iwutils.h"
|
||||
#include "iwpool.h"
|
||||
#include "iwrb.h"
|
||||
#include "iwconv.h"
|
||||
|
||||
int init_suite(void) {
|
||||
static int init_suite(void) {
|
||||
return iw_init();
|
||||
}
|
||||
|
||||
int clean_suite(void) {
|
||||
static int clean_suite(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -25,7 +26,7 @@ static const char* _replace_mapper1(const char *key, void *op) {
|
||||
}
|
||||
}
|
||||
|
||||
void test_iwu_replace_into(void) {
|
||||
static void test_iwu_replace_into(void) {
|
||||
IWXSTR *res = 0;
|
||||
const char *data = "What you said about my {}?";
|
||||
const char *keys[] = { "{}", "$", "?", "you", "my" };
|
||||
@@ -37,10 +38,10 @@ void test_iwu_replace_into(void) {
|
||||
iwxstr_destroy(res);
|
||||
}
|
||||
|
||||
void test_iwpool_split_string(void) {
|
||||
static void test_iwpool_split_string(void) {
|
||||
IWPOOL *pool = iwpool_create(128);
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(pool);
|
||||
char **res = iwpool_split_string(pool, " foo , bar:baz,,z,", ",:", true);
|
||||
const char **res = iwpool_split_string(pool, " foo , bar:baz,,z,", ",:", true);
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(res);
|
||||
int i = 0;
|
||||
for ( ; res[i]; ++i) {
|
||||
@@ -120,7 +121,7 @@ void test_iwpool_split_string(void) {
|
||||
iwpool_destroy(pool);
|
||||
}
|
||||
|
||||
void test_iwpool_printf(void) {
|
||||
static void test_iwpool_printf(void) {
|
||||
IWPOOL *pool = iwpool_create(128);
|
||||
CU_ASSERT_PTR_NOT_NULL_FATAL(pool);
|
||||
const char *res = iwpool_printf(pool, "%s=%s", "foo", "bar");
|
||||
@@ -129,7 +130,7 @@ void test_iwpool_printf(void) {
|
||||
iwpool_destroy(pool);
|
||||
}
|
||||
|
||||
void test_iwrb1(void) {
|
||||
static void test_iwrb1(void) {
|
||||
int *p;
|
||||
IWRB_ITER iter;
|
||||
IWRB *rb = iwrb_create(sizeof(int), 7);
|
||||
@@ -194,7 +195,14 @@ void test_iwrb1(void) {
|
||||
CU_ASSERT_PTR_NULL(rb);
|
||||
}
|
||||
|
||||
int main() {
|
||||
static void iwitoa_issue48(void) {
|
||||
char buf[IWNUMBUF_SIZE];
|
||||
int len = iwitoa(INT64_MIN, buf, sizeof(buf));
|
||||
CU_ASSERT_EQUAL(len, 20);
|
||||
CU_ASSERT_STRING_EQUAL("-9223372036854775808", buf);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
CU_pSuite pSuite = NULL;
|
||||
|
||||
/* Initialize the CUnit test registry */
|
||||
@@ -214,7 +222,8 @@ int main() {
|
||||
if ( (NULL == CU_add_test(pSuite, "test_iwu_replace_into", test_iwu_replace_into))
|
||||
|| (NULL == CU_add_test(pSuite, "test_iwpool_split_string", test_iwpool_split_string))
|
||||
|| (NULL == CU_add_test(pSuite, "test_iwpool_printf", test_iwpool_printf))
|
||||
|| (NULL == CU_add_test(pSuite, "test_iwrb1", test_iwrb1))) {
|
||||
|| (NULL == CU_add_test(pSuite, "test_iwrb1", test_iwrb1))
|
||||
|| (NULL == CU_add_test(pSuite, "iwitoa_issue48", iwitoa_issue48))) {
|
||||
CU_cleanup_registry();
|
||||
return CU_get_error();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,329 @@
|
||||
/* -*- mode: c; c-basic-offset: 2; tab-width: 2; indent-tabs-mode: nil -*- */
|
||||
/*
|
||||
* Copyright (c) 2015 Steven G. Johnson, Jiahao Chen, Peter Colberg, Tony Kelman, Scott P. Jones, and other
|
||||
* contributors.
|
||||
* Copyright (c) 2009 Public Software Group e. V., Berlin, Germany
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This library contains derived data from a modified version of the
|
||||
* Unicode data files.
|
||||
*
|
||||
* The original data files are available at
|
||||
* http://www.unicode.org/Public/UNIDATA/
|
||||
*
|
||||
* Please notice the copyright statement in the file "utf8proc_data.c".
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* File name: utf8proc.c
|
||||
*
|
||||
* Description:
|
||||
* Implementation of libutf8proc.
|
||||
*/
|
||||
|
||||
|
||||
#include "utf8proc.h"
|
||||
|
||||
UTF8PROC_DLLEXPORT const utf8proc_int8_t utf8proc_utf8class[256] = {
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
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,
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
};
|
||||
|
||||
#define UTF8PROC_HANGUL_SBASE 0xAC00
|
||||
#define UTF8PROC_HANGUL_LBASE 0x1100
|
||||
#define UTF8PROC_HANGUL_VBASE 0x1161
|
||||
#define UTF8PROC_HANGUL_TBASE 0x11A7
|
||||
#define UTF8PROC_HANGUL_LCOUNT 19
|
||||
#define UTF8PROC_HANGUL_VCOUNT 21
|
||||
#define UTF8PROC_HANGUL_TCOUNT 28
|
||||
#define UTF8PROC_HANGUL_NCOUNT 588
|
||||
#define UTF8PROC_HANGUL_SCOUNT 11172
|
||||
/* END is exclusive */
|
||||
#define UTF8PROC_HANGUL_L_START 0x1100
|
||||
#define UTF8PROC_HANGUL_L_END 0x115A
|
||||
#define UTF8PROC_HANGUL_L_FILLER 0x115F
|
||||
#define UTF8PROC_HANGUL_V_START 0x1160
|
||||
#define UTF8PROC_HANGUL_V_END 0x11A3
|
||||
#define UTF8PROC_HANGUL_T_START 0x11A8
|
||||
#define UTF8PROC_HANGUL_T_END 0x11FA
|
||||
#define UTF8PROC_HANGUL_S_START 0xAC00
|
||||
#define UTF8PROC_HANGUL_S_END 0xD7A4
|
||||
|
||||
/* Should follow semantic-versioning rules (semver.org) based on API
|
||||
compatibility. (Note that the shared-library version number will
|
||||
be different, being based on ABI compatibility.): */
|
||||
#define STRINGIZEx(x) #x
|
||||
#define STRINGIZE(x) STRINGIZEx(x)
|
||||
|
||||
UTF8PROC_DLLEXPORT const char* utf8proc_version(void) {
|
||||
return STRINGIZE(UTF8PROC_VERSION_MAJOR) "." STRINGIZE(UTF8PROC_VERSION_MINOR) "." STRINGIZE(UTF8PROC_VERSION_PATCH)
|
||||
"";
|
||||
}
|
||||
|
||||
UTF8PROC_DLLEXPORT const char* utf8proc_errmsg(utf8proc_ssize_t errcode) {
|
||||
switch (errcode) {
|
||||
case UTF8PROC_ERROR_NOMEM:
|
||||
return "Memory for processing UTF-8 data could not be allocated.";
|
||||
case UTF8PROC_ERROR_OVERFLOW:
|
||||
return "UTF-8 string is too long to be processed.";
|
||||
case UTF8PROC_ERROR_INVALIDUTF8:
|
||||
return "Invalid UTF-8 string";
|
||||
case UTF8PROC_ERROR_NOTASSIGNED:
|
||||
return "Unassigned Unicode code point found in UTF-8 string.";
|
||||
case UTF8PROC_ERROR_INVALIDOPTS:
|
||||
return "Invalid options for UTF-8 processing chosen.";
|
||||
default:
|
||||
return "An unknown error occurred while processing UTF-8 data.";
|
||||
}
|
||||
}
|
||||
|
||||
#define utf_cont(ch) (((ch) & 0xc0) == 0x80)
|
||||
|
||||
UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_iterate(
|
||||
const utf8proc_uint8_t *str,
|
||||
utf8proc_ssize_t strlen,
|
||||
utf8proc_int32_t *dst
|
||||
) {
|
||||
utf8proc_uint32_t uc;
|
||||
const utf8proc_uint8_t *end;
|
||||
|
||||
*dst = -1;
|
||||
if (!strlen) {
|
||||
return 0;
|
||||
}
|
||||
end = str + ((strlen < 0) ? 4 : strlen);
|
||||
uc = *str++;
|
||||
if (uc < 0x80) {
|
||||
*dst = uc;
|
||||
return 1;
|
||||
}
|
||||
// Must be between 0xc2 and 0xf4 inclusive to be valid
|
||||
if ((uc - 0xc2) > (0xf4 - 0xc2)) {
|
||||
return UTF8PROC_ERROR_INVALIDUTF8;
|
||||
}
|
||||
if (uc < 0xe0) { // 2-byte sequence
|
||||
// Must have valid continuation character
|
||||
if ((str >= end) || !utf_cont(*str)) {
|
||||
return UTF8PROC_ERROR_INVALIDUTF8;
|
||||
}
|
||||
*dst = ((uc & 0x1f) << 6) | (*str & 0x3f);
|
||||
return 2;
|
||||
}
|
||||
if (uc < 0xf0) { // 3-byte sequence
|
||||
if ((str + 1 >= end) || !utf_cont(*str) || !utf_cont(str[1])) {
|
||||
return UTF8PROC_ERROR_INVALIDUTF8;
|
||||
}
|
||||
// Check for surrogate chars
|
||||
if ((uc == 0xed) && (*str > 0x9f)) {
|
||||
return UTF8PROC_ERROR_INVALIDUTF8;
|
||||
}
|
||||
uc = ((uc & 0xf) << 12) | ((*str & 0x3f) << 6) | (str[1] & 0x3f);
|
||||
if (uc < 0x800) {
|
||||
return UTF8PROC_ERROR_INVALIDUTF8;
|
||||
}
|
||||
*dst = uc;
|
||||
return 3;
|
||||
}
|
||||
// 4-byte sequence
|
||||
// Must have 3 valid continuation characters
|
||||
if ((str + 2 >= end) || !utf_cont(*str) || !utf_cont(str[1]) || !utf_cont(str[2])) {
|
||||
return UTF8PROC_ERROR_INVALIDUTF8;
|
||||
}
|
||||
// Make sure in correct range (0x10000 - 0x10ffff)
|
||||
if (uc == 0xf0) {
|
||||
if (*str < 0x90) {
|
||||
return UTF8PROC_ERROR_INVALIDUTF8;
|
||||
}
|
||||
} else if (uc == 0xf4) {
|
||||
if (*str > 0x8f) {
|
||||
return UTF8PROC_ERROR_INVALIDUTF8;
|
||||
}
|
||||
}
|
||||
*dst = ((uc & 7) << 18) | ((*str & 0x3f) << 12) | ((str[1] & 0x3f) << 6) | (str[2] & 0x3f);
|
||||
return 4;
|
||||
}
|
||||
|
||||
UTF8PROC_DLLEXPORT utf8proc_bool utf8proc_codepoint_valid(utf8proc_int32_t uc) {
|
||||
return (((utf8proc_uint32_t) uc) - 0xd800 > 0x07ff) && ((utf8proc_uint32_t) uc < 0x110000);
|
||||
}
|
||||
|
||||
UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_encode_char(utf8proc_int32_t uc, utf8proc_uint8_t *dst) {
|
||||
if (uc < 0x00) {
|
||||
return 0;
|
||||
} else if (uc < 0x80) {
|
||||
dst[0] = (utf8proc_uint8_t) uc;
|
||||
return 1;
|
||||
} else if (uc < 0x800) {
|
||||
dst[0] = (utf8proc_uint8_t) (0xC0 + (uc >> 6));
|
||||
dst[1] = (utf8proc_uint8_t) (0x80 + (uc & 0x3F));
|
||||
return 2;
|
||||
// Note: we allow encoding 0xd800-0xdfff here, so as not to change
|
||||
// the API, however, these are actually invalid in UTF-8
|
||||
} else if (uc < 0x10000) {
|
||||
dst[0] = (utf8proc_uint8_t) (0xE0 + (uc >> 12));
|
||||
dst[1] = (utf8proc_uint8_t) (0x80 + ((uc >> 6) & 0x3F));
|
||||
dst[2] = (utf8proc_uint8_t) (0x80 + (uc & 0x3F));
|
||||
return 3;
|
||||
} else if (uc < 0x110000) {
|
||||
dst[0] = (utf8proc_uint8_t) (0xF0 + (uc >> 18));
|
||||
dst[1] = (utf8proc_uint8_t) (0x80 + ((uc >> 12) & 0x3F));
|
||||
dst[2] = (utf8proc_uint8_t) (0x80 + ((uc >> 6) & 0x3F));
|
||||
dst[3] = (utf8proc_uint8_t) (0x80 + (uc & 0x3F));
|
||||
return 4;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* internal "unsafe" version that does not check whether uc is in range */
|
||||
static utf8proc_ssize_t unsafe_encode_char(utf8proc_int32_t uc, utf8proc_uint8_t *dst) {
|
||||
if (uc < 0x00) {
|
||||
return 0;
|
||||
} else if (uc < 0x80) {
|
||||
dst[0] = (utf8proc_uint8_t) uc;
|
||||
return 1;
|
||||
} else if (uc < 0x800) {
|
||||
dst[0] = (utf8proc_uint8_t) (0xC0 + (uc >> 6));
|
||||
dst[1] = (utf8proc_uint8_t) (0x80 + (uc & 0x3F));
|
||||
return 2;
|
||||
} else if (uc == 0xFFFF) {
|
||||
dst[0] = (utf8proc_uint8_t) 0xFF;
|
||||
return 1;
|
||||
} else if (uc == 0xFFFE) {
|
||||
dst[0] = (utf8proc_uint8_t) 0xFE;
|
||||
return 1;
|
||||
} else if (uc < 0x10000) {
|
||||
dst[0] = (utf8proc_uint8_t) (0xE0 + (uc >> 12));
|
||||
dst[1] = (utf8proc_uint8_t) (0x80 + ((uc >> 6) & 0x3F));
|
||||
dst[2] = (utf8proc_uint8_t) (0x80 + (uc & 0x3F));
|
||||
return 3;
|
||||
} else if (uc < 0x110000) {
|
||||
dst[0] = (utf8proc_uint8_t) (0xF0 + (uc >> 18));
|
||||
dst[1] = (utf8proc_uint8_t) (0x80 + ((uc >> 12) & 0x3F));
|
||||
dst[2] = (utf8proc_uint8_t) (0x80 + ((uc >> 6) & 0x3F));
|
||||
dst[3] = (utf8proc_uint8_t) (0x80 + (uc & 0x3F));
|
||||
return 4;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* return whether there is a grapheme break between boundclasses lbc and tbc
|
||||
(according to the definition of extended grapheme clusters)
|
||||
|
||||
Rule numbering refers to TR29 Version 29 (Unicode 9.0.0):
|
||||
http://www.unicode.org/reports/tr29/tr29-29.html
|
||||
|
||||
CAVEATS:
|
||||
Please note that evaluation of GB10 (grapheme breaks between emoji zwj sequences)
|
||||
and GB 12/13 (regional indicator code points) require knowledge of previous characters
|
||||
and are thus not handled by this function. This may result in an incorrect break before
|
||||
an E_Modifier class codepoint and an incorrectly missing break between two
|
||||
REGIONAL_INDICATOR class code points if such support does not exist in the caller.
|
||||
|
||||
See the special support in grapheme_break_extended, for required bookkeeping by the caller.
|
||||
*/
|
||||
static utf8proc_bool grapheme_break_simple(int lbc, int tbc) {
|
||||
return (lbc == UTF8PROC_BOUNDCLASS_START) ? true // GB1
|
||||
: ( lbc == UTF8PROC_BOUNDCLASS_CR // GB3
|
||||
&& tbc == UTF8PROC_BOUNDCLASS_LF) ? false // ---
|
||||
: (lbc >= UTF8PROC_BOUNDCLASS_CR && lbc <= UTF8PROC_BOUNDCLASS_CONTROL) ? true // GB4
|
||||
: (tbc >= UTF8PROC_BOUNDCLASS_CR && tbc <= UTF8PROC_BOUNDCLASS_CONTROL) ? true // GB5
|
||||
: ( lbc == UTF8PROC_BOUNDCLASS_L // GB6
|
||||
&& ( tbc == UTF8PROC_BOUNDCLASS_L // ---
|
||||
|| tbc == UTF8PROC_BOUNDCLASS_V // ---
|
||||
|| tbc == UTF8PROC_BOUNDCLASS_LV // ---
|
||||
|| tbc == UTF8PROC_BOUNDCLASS_LVT)) ? false // ---
|
||||
: ( ( lbc == UTF8PROC_BOUNDCLASS_LV // GB7
|
||||
|| lbc == UTF8PROC_BOUNDCLASS_V) // ---
|
||||
&& ( tbc == UTF8PROC_BOUNDCLASS_V // ---
|
||||
|| tbc == UTF8PROC_BOUNDCLASS_T)) ? false // ---
|
||||
: ( ( lbc == UTF8PROC_BOUNDCLASS_LVT // GB8
|
||||
|| lbc == UTF8PROC_BOUNDCLASS_T) // ---
|
||||
&& tbc == UTF8PROC_BOUNDCLASS_T) ? false // ---
|
||||
: ( tbc == UTF8PROC_BOUNDCLASS_EXTEND // GB9
|
||||
|| tbc == UTF8PROC_BOUNDCLASS_ZWJ // ---
|
||||
|| tbc == UTF8PROC_BOUNDCLASS_SPACINGMARK // GB9a
|
||||
|| lbc == UTF8PROC_BOUNDCLASS_PREPEND) ? false // GB9b
|
||||
: ( ( lbc == UTF8PROC_BOUNDCLASS_E_BASE // GB10 (requires additional
|
||||
// handling below)
|
||||
|| lbc == UTF8PROC_BOUNDCLASS_E_BASE_GAZ) // ----
|
||||
&& tbc == UTF8PROC_BOUNDCLASS_E_MODIFIER) ? false // ----
|
||||
: ( lbc == UTF8PROC_BOUNDCLASS_ZWJ // GB11
|
||||
&& ( tbc == UTF8PROC_BOUNDCLASS_GLUE_AFTER_ZWJ // ----
|
||||
|| tbc == UTF8PROC_BOUNDCLASS_E_BASE_GAZ)) ? false // ----
|
||||
: ( lbc == UTF8PROC_BOUNDCLASS_REGIONAL_INDICATOR // GB12/13 (requires additional
|
||||
// handling below)
|
||||
&& tbc == UTF8PROC_BOUNDCLASS_REGIONAL_INDICATOR) ? false // ----
|
||||
: true; // GB999
|
||||
}
|
||||
|
||||
static utf8proc_bool grapheme_break_extended(int lbc, int tbc, utf8proc_int32_t *state) {
|
||||
int lbc_override = ((state && *state != UTF8PROC_BOUNDCLASS_START)
|
||||
? *state : lbc);
|
||||
utf8proc_bool break_permitted = grapheme_break_simple(lbc_override, tbc);
|
||||
if (state) {
|
||||
// Special support for GB 12/13 made possible by GB999. After two RI
|
||||
// class codepoints we want to force a break. Do this by resetting the
|
||||
// second RI's bound class to UTF8PROC_BOUNDCLASS_OTHER, to force a break
|
||||
// after that character according to GB999 (unless of course such a break is
|
||||
// forbidden by a different rule such as GB9).
|
||||
if ((*state == tbc) && (tbc == UTF8PROC_BOUNDCLASS_REGIONAL_INDICATOR)) {
|
||||
*state = UTF8PROC_BOUNDCLASS_OTHER;
|
||||
}
|
||||
// Special support for GB10. Fold any EXTEND codepoints into the previous
|
||||
// boundclass if we're dealing with an emoji base boundclass.
|
||||
else if ( ( (*state == UTF8PROC_BOUNDCLASS_E_BASE)
|
||||
|| (*state == UTF8PROC_BOUNDCLASS_E_BASE_GAZ))
|
||||
&& (tbc == UTF8PROC_BOUNDCLASS_EXTEND)) {
|
||||
*state = UTF8PROC_BOUNDCLASS_E_BASE;
|
||||
} else {
|
||||
*state = tbc;
|
||||
}
|
||||
}
|
||||
return break_permitted;
|
||||
}
|
||||
|
||||
static utf8proc_int32_t seqindex_decode_entry(const utf8proc_uint16_t **entry) {
|
||||
utf8proc_int32_t entry_cp = **entry;
|
||||
if ((entry_cp & 0xF800) == 0xD800) {
|
||||
*entry = *entry + 1;
|
||||
entry_cp = ((entry_cp & 0x03FF) << 10) | (**entry & 0x03FF);
|
||||
entry_cp += 0x10000;
|
||||
}
|
||||
return entry_cp;
|
||||
}
|
||||
@@ -0,0 +1,733 @@
|
||||
/*
|
||||
* Copyright (c) 2015 Steven G. Johnson, Jiahao Chen, Peter Colberg, Tony Kelman, Scott P. Jones, and other
|
||||
* contributors.
|
||||
* Copyright (c) 2009 Public Software Group e. V., Berlin, Germany
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @mainpage
|
||||
*
|
||||
* utf8proc is a free/open-source (MIT/expat licensed) C library
|
||||
* providing Unicode normalization, case-folding, and other operations
|
||||
* for strings in the UTF-8 encoding, supporting Unicode version
|
||||
* 9.0.0. See the utf8proc home page (http://julialang.org/utf8proc/)
|
||||
* for downloads and other information, or the source code on github
|
||||
* (https://github.com/JuliaLang/utf8proc).
|
||||
*
|
||||
* For the utf8proc API documentation, see: @ref utf8proc.h
|
||||
*
|
||||
* The features of utf8proc include:
|
||||
*
|
||||
* - Transformation of strings (@ref utf8proc_map) to:
|
||||
* - decompose (@ref UTF8PROC_DECOMPOSE) or compose (@ref UTF8PROC_COMPOSE) Unicode combining characters
|
||||
*(http://en.wikipedia.org/wiki/Combining_character)
|
||||
* - canonicalize Unicode compatibility characters (@ref UTF8PROC_COMPAT)
|
||||
* - strip "ignorable" (@ref UTF8PROC_IGNORE) characters, control characters (@ref UTF8PROC_STRIPCC), or combining
|
||||
* characters such as accents (@ref UTF8PROC_STRIPMARK)
|
||||
* - case-folding (@ref UTF8PROC_CASEFOLD)
|
||||
* - Unicode normalization: @ref utf8proc_NFD, @ref utf8proc_NFC, @ref utf8proc_NFKD, @ref utf8proc_NFKC
|
||||
* - Detecting grapheme boundaries (@ref utf8proc_grapheme_break and @ref UTF8PROC_CHARBOUND)
|
||||
* - Character-width computation: @ref utf8proc_charwidth
|
||||
* - Classification of characters by Unicode category: @ref utf8proc_category and @ref utf8proc_category_string
|
||||
* - Encode (@ref utf8proc_encode_char) and decode (@ref utf8proc_iterate) Unicode codepoints to/from UTF-8.
|
||||
*/
|
||||
|
||||
/** @file */
|
||||
|
||||
#ifndef UTF8PROC_H
|
||||
#define UTF8PROC_H
|
||||
|
||||
/** @name API version
|
||||
*
|
||||
* The utf8proc API version MAJOR.MINOR.PATCH, following
|
||||
* semantic-versioning rules (http://semver.org) based on API
|
||||
* compatibility.
|
||||
*
|
||||
* This is also returned at runtime by @ref utf8proc_version; however, the
|
||||
* runtime version may append a string like "-dev" to the version number
|
||||
* for prerelease versions.
|
||||
*
|
||||
* @note The shared-library version number in the Makefile
|
||||
* (and CMakeLists.txt, and MANIFEST) may be different,
|
||||
* being based on ABI compatibility rather than API compatibility.
|
||||
*/
|
||||
/** @{ */
|
||||
/** The MAJOR version number (increased when backwards API compatibility is broken). */
|
||||
#define UTF8PROC_VERSION_MAJOR 2
|
||||
/** The MINOR version number (increased when new functionality is added in a backwards-compatible manner). */
|
||||
#define UTF8PROC_VERSION_MINOR 2
|
||||
/** The PATCH version (increased for fixes that do not change the API). */
|
||||
#define UTF8PROC_VERSION_PATCH 0
|
||||
/** @} */
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1800
|
||||
// MSVC prior to 2013 lacked stdbool.h and inttypes.h
|
||||
typedef signed char utf8proc_int8_t;
|
||||
typedef unsigned char utf8proc_uint8_t;
|
||||
typedef short utf8proc_int16_t;
|
||||
typedef unsigned short utf8proc_uint16_t;
|
||||
typedef int utf8proc_int32_t;
|
||||
typedef unsigned int utf8proc_uint32_t;
|
||||
# ifdef _WIN64
|
||||
typedef __int64 utf8proc_ssize_t;
|
||||
typedef unsigned __int64 utf8proc_size_t;
|
||||
# else
|
||||
typedef int utf8proc_ssize_t;
|
||||
typedef unsigned int utf8proc_size_t;
|
||||
# endif
|
||||
# ifndef __cplusplus
|
||||
// emulate C99 bool
|
||||
typedef unsigned char utf8proc_bool;
|
||||
# ifndef __bool_true_false_are_defined
|
||||
# define false 0
|
||||
# define true 1
|
||||
# define __bool_true_false_are_defined 1
|
||||
# endif
|
||||
# else
|
||||
typedef bool utf8proc_bool;
|
||||
# endif
|
||||
#else
|
||||
# include <stddef.h>
|
||||
# include <stdbool.h>
|
||||
# include <inttypes.h>
|
||||
typedef int8_t utf8proc_int8_t;
|
||||
typedef uint8_t utf8proc_uint8_t;
|
||||
typedef int16_t utf8proc_int16_t;
|
||||
typedef uint16_t utf8proc_uint16_t;
|
||||
typedef int32_t utf8proc_int32_t;
|
||||
typedef uint32_t utf8proc_uint32_t;
|
||||
typedef size_t utf8proc_size_t;
|
||||
typedef ptrdiff_t utf8proc_ssize_t;
|
||||
typedef bool utf8proc_bool;
|
||||
#endif
|
||||
#include <limits.h>
|
||||
|
||||
#define UTF8PROC_STATIC
|
||||
|
||||
#ifdef UTF8PROC_STATIC
|
||||
# define UTF8PROC_DLLEXPORT
|
||||
#else
|
||||
# ifdef _WIN32
|
||||
# ifdef UTF8PROC_EXPORTS
|
||||
# define UTF8PROC_DLLEXPORT __declspec(dllexport)
|
||||
# else
|
||||
# define UTF8PROC_DLLEXPORT __declspec(dllimport)
|
||||
# endif
|
||||
# elif __GNUC__ >= 4
|
||||
# define UTF8PROC_DLLEXPORT __attribute__((visibility("default")))
|
||||
# else
|
||||
# define UTF8PROC_DLLEXPORT
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef SSIZE_MAX
|
||||
#define SSIZE_MAX ((size_t) SIZE_MAX / 2)
|
||||
#endif
|
||||
|
||||
#ifndef UINT16_MAX
|
||||
# define UINT16_MAX 65535U
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Option flags used by several functions in the library.
|
||||
*/
|
||||
typedef enum {
|
||||
/** The given UTF-8 input is NULL terminated. */
|
||||
UTF8PROC_NULLTERM = (1 << 0),
|
||||
/** Unicode Versioning Stability has to be respected. */
|
||||
UTF8PROC_STABLE = (1 << 1),
|
||||
/** Compatibility decomposition (i.e. formatting information is lost). */
|
||||
UTF8PROC_COMPAT = (1 << 2),
|
||||
/** Return a result with decomposed characters. */
|
||||
UTF8PROC_COMPOSE = (1 << 3),
|
||||
/** Return a result with decomposed characters. */
|
||||
UTF8PROC_DECOMPOSE = (1 << 4),
|
||||
/** Strip "default ignorable characters" such as SOFT-HYPHEN or ZERO-WIDTH-SPACE. */
|
||||
UTF8PROC_IGNORE = (1 << 5),
|
||||
/** Return an error, if the input contains unassigned codepoints. */
|
||||
UTF8PROC_REJECTNA = (1 << 6),
|
||||
/**
|
||||
* Indicating that NLF-sequences (LF, CRLF, CR, NEL) are representing a
|
||||
* line break, and should be converted to the codepoint for line
|
||||
* separation (LS).
|
||||
*/
|
||||
UTF8PROC_NLF2LS = (1 << 7),
|
||||
/**
|
||||
* Indicating that NLF-sequences are representing a paragraph break, and
|
||||
* should be converted to the codepoint for paragraph separation
|
||||
* (PS).
|
||||
*/
|
||||
UTF8PROC_NLF2PS = (1 << 8),
|
||||
/** Indicating that the meaning of NLF-sequences is unknown. */
|
||||
UTF8PROC_NLF2LF = (UTF8PROC_NLF2LS | UTF8PROC_NLF2PS),
|
||||
/** Strips and/or convers control characters.
|
||||
*
|
||||
* NLF-sequences are transformed into space, except if one of the
|
||||
* NLF2LS/PS/LF options is given. HorizontalTab (HT) and FormFeed (FF)
|
||||
* are treated as a NLF-sequence in this case. All other control
|
||||
* characters are simply removed.
|
||||
*/
|
||||
UTF8PROC_STRIPCC = (1 << 9),
|
||||
/**
|
||||
* Performs unicode case folding, to be able to do a case-insensitive
|
||||
* string comparison.
|
||||
*/
|
||||
UTF8PROC_CASEFOLD = (1 << 10),
|
||||
/**
|
||||
* Inserts 0xFF bytes at the beginning of each sequence which is
|
||||
* representing a single grapheme cluster (see UAX#29).
|
||||
*/
|
||||
UTF8PROC_CHARBOUND = (1 << 11),
|
||||
/** Lumps certain characters together.
|
||||
*
|
||||
* E.g. HYPHEN U+2010 and MINUS U+2212 to ASCII "-". See lump.md for details.
|
||||
*
|
||||
* If NLF2LF is set, this includes a transformation of paragraph and
|
||||
* line separators to ASCII line-feed (LF).
|
||||
*/
|
||||
UTF8PROC_LUMP = (1 << 12),
|
||||
/** Strips all character markings.
|
||||
*
|
||||
* This includes non-spacing, spacing and enclosing (i.e. accents).
|
||||
* @note This option works only with @ref UTF8PROC_COMPOSE or
|
||||
* @ref UTF8PROC_DECOMPOSE
|
||||
*/
|
||||
UTF8PROC_STRIPMARK = (1 << 13),
|
||||
/**
|
||||
* Strip unassigned codepoints.
|
||||
*/
|
||||
UTF8PROC_STRIPNA = (1 << 14),
|
||||
} utf8proc_option_t;
|
||||
|
||||
/** @name Error codes
|
||||
* Error codes being returned by almost all functions.
|
||||
*/
|
||||
/** @{ */
|
||||
/** Memory could not be allocated. */
|
||||
#define UTF8PROC_ERROR_NOMEM -1
|
||||
/** The given string is too long to be processed. */
|
||||
#define UTF8PROC_ERROR_OVERFLOW -2
|
||||
/** The given string is not a legal UTF-8 string. */
|
||||
#define UTF8PROC_ERROR_INVALIDUTF8 -3
|
||||
/** The @ref UTF8PROC_REJECTNA flag was set and an unassigned codepoint was found. */
|
||||
#define UTF8PROC_ERROR_NOTASSIGNED -4
|
||||
/** Invalid options have been used. */
|
||||
#define UTF8PROC_ERROR_INVALIDOPTS -5
|
||||
/** @} */
|
||||
|
||||
/* @name Types */
|
||||
|
||||
/** Holds the value of a property. */
|
||||
typedef utf8proc_int16_t utf8proc_propval_t;
|
||||
|
||||
/** Struct containing information about a codepoint. */
|
||||
typedef struct utf8proc_property_struct {
|
||||
/**
|
||||
* Unicode category.
|
||||
* @see utf8proc_category_t.
|
||||
*/
|
||||
utf8proc_propval_t category;
|
||||
utf8proc_propval_t combining_class;
|
||||
/**
|
||||
* Bidirectional class.
|
||||
* @see utf8proc_bidi_class_t.
|
||||
*/
|
||||
utf8proc_propval_t bidi_class;
|
||||
/**
|
||||
* @anchor Decomposition type.
|
||||
* @see utf8proc_decomp_type_t.
|
||||
*/
|
||||
utf8proc_propval_t decomp_type;
|
||||
utf8proc_uint16_t decomp_seqindex;
|
||||
utf8proc_uint16_t casefold_seqindex;
|
||||
utf8proc_uint16_t uppercase_seqindex;
|
||||
utf8proc_uint16_t lowercase_seqindex;
|
||||
utf8proc_uint16_t titlecase_seqindex;
|
||||
utf8proc_uint16_t comb_index;
|
||||
unsigned bidi_mirrored : 1;
|
||||
unsigned comp_exclusion : 1;
|
||||
/**
|
||||
* Can this codepoint be ignored?
|
||||
*
|
||||
* Used by @ref utf8proc_decompose_char when @ref UTF8PROC_IGNORE is
|
||||
* passed as an option.
|
||||
*/
|
||||
unsigned ignorable : 1;
|
||||
unsigned control_boundary : 1;
|
||||
/** The width of the codepoint. */
|
||||
unsigned charwidth : 2;
|
||||
unsigned pad : 2;
|
||||
/**
|
||||
* Boundclass.
|
||||
* @see utf8proc_boundclass_t.
|
||||
*/
|
||||
unsigned boundclass : 8;
|
||||
} utf8proc_property_t;
|
||||
|
||||
/** Unicode categories. */
|
||||
typedef enum {
|
||||
UTF8PROC_CATEGORY_CN = 0, /**< Other, not assigned */
|
||||
UTF8PROC_CATEGORY_LU = 1, /**< Letter, uppercase */
|
||||
UTF8PROC_CATEGORY_LL = 2, /**< Letter, lowercase */
|
||||
UTF8PROC_CATEGORY_LT = 3, /**< Letter, titlecase */
|
||||
UTF8PROC_CATEGORY_LM = 4, /**< Letter, modifier */
|
||||
UTF8PROC_CATEGORY_LO = 5, /**< Letter, other */
|
||||
UTF8PROC_CATEGORY_MN = 6, /**< Mark, nonspacing */
|
||||
UTF8PROC_CATEGORY_MC = 7, /**< Mark, spacing combining */
|
||||
UTF8PROC_CATEGORY_ME = 8, /**< Mark, enclosing */
|
||||
UTF8PROC_CATEGORY_ND = 9, /**< Number, decimal digit */
|
||||
UTF8PROC_CATEGORY_NL = 10, /**< Number, letter */
|
||||
UTF8PROC_CATEGORY_NO = 11, /**< Number, other */
|
||||
UTF8PROC_CATEGORY_PC = 12, /**< Punctuation, connector */
|
||||
UTF8PROC_CATEGORY_PD = 13, /**< Punctuation, dash */
|
||||
UTF8PROC_CATEGORY_PS = 14, /**< Punctuation, open */
|
||||
UTF8PROC_CATEGORY_PE = 15, /**< Punctuation, close */
|
||||
UTF8PROC_CATEGORY_PI = 16, /**< Punctuation, initial quote */
|
||||
UTF8PROC_CATEGORY_PF = 17, /**< Punctuation, final quote */
|
||||
UTF8PROC_CATEGORY_PO = 18, /**< Punctuation, other */
|
||||
UTF8PROC_CATEGORY_SM = 19, /**< Symbol, math */
|
||||
UTF8PROC_CATEGORY_SC = 20, /**< Symbol, currency */
|
||||
UTF8PROC_CATEGORY_SK = 21, /**< Symbol, modifier */
|
||||
UTF8PROC_CATEGORY_SO = 22, /**< Symbol, other */
|
||||
UTF8PROC_CATEGORY_ZS = 23, /**< Separator, space */
|
||||
UTF8PROC_CATEGORY_ZL = 24, /**< Separator, line */
|
||||
UTF8PROC_CATEGORY_ZP = 25, /**< Separator, paragraph */
|
||||
UTF8PROC_CATEGORY_CC = 26, /**< Other, control */
|
||||
UTF8PROC_CATEGORY_CF = 27, /**< Other, format */
|
||||
UTF8PROC_CATEGORY_CS = 28, /**< Other, surrogate */
|
||||
UTF8PROC_CATEGORY_CO = 29, /**< Other, private use */
|
||||
} utf8proc_category_t;
|
||||
|
||||
/** Bidirectional character classes. */
|
||||
typedef enum {
|
||||
UTF8PROC_BIDI_CLASS_L = 1, /**< Left-to-Right */
|
||||
UTF8PROC_BIDI_CLASS_LRE = 2, /**< Left-to-Right Embedding */
|
||||
UTF8PROC_BIDI_CLASS_LRO = 3, /**< Left-to-Right Override */
|
||||
UTF8PROC_BIDI_CLASS_R = 4, /**< Right-to-Left */
|
||||
UTF8PROC_BIDI_CLASS_AL = 5, /**< Right-to-Left Arabic */
|
||||
UTF8PROC_BIDI_CLASS_RLE = 6, /**< Right-to-Left Embedding */
|
||||
UTF8PROC_BIDI_CLASS_RLO = 7, /**< Right-to-Left Override */
|
||||
UTF8PROC_BIDI_CLASS_PDF = 8, /**< Pop Directional Format */
|
||||
UTF8PROC_BIDI_CLASS_EN = 9, /**< European Number */
|
||||
UTF8PROC_BIDI_CLASS_ES = 10, /**< European Separator */
|
||||
UTF8PROC_BIDI_CLASS_ET = 11, /**< European Number Terminator */
|
||||
UTF8PROC_BIDI_CLASS_AN = 12, /**< Arabic Number */
|
||||
UTF8PROC_BIDI_CLASS_CS = 13, /**< Common Number Separator */
|
||||
UTF8PROC_BIDI_CLASS_NSM = 14, /**< Nonspacing Mark */
|
||||
UTF8PROC_BIDI_CLASS_BN = 15, /**< Boundary Neutral */
|
||||
UTF8PROC_BIDI_CLASS_B = 16, /**< Paragraph Separator */
|
||||
UTF8PROC_BIDI_CLASS_S = 17, /**< Segment Separator */
|
||||
UTF8PROC_BIDI_CLASS_WS = 18, /**< Whitespace */
|
||||
UTF8PROC_BIDI_CLASS_ON = 19, /**< Other Neutrals */
|
||||
UTF8PROC_BIDI_CLASS_LRI = 20, /**< Left-to-Right Isolate */
|
||||
UTF8PROC_BIDI_CLASS_RLI = 21, /**< Right-to-Left Isolate */
|
||||
UTF8PROC_BIDI_CLASS_FSI = 22, /**< First Strong Isolate */
|
||||
UTF8PROC_BIDI_CLASS_PDI = 23, /**< Pop Directional Isolate */
|
||||
} utf8proc_bidi_class_t;
|
||||
|
||||
/** Decomposition type. */
|
||||
typedef enum {
|
||||
UTF8PROC_DECOMP_TYPE_FONT = 1, /**< Font */
|
||||
UTF8PROC_DECOMP_TYPE_NOBREAK = 2, /**< Nobreak */
|
||||
UTF8PROC_DECOMP_TYPE_INITIAL = 3, /**< Initial */
|
||||
UTF8PROC_DECOMP_TYPE_MEDIAL = 4, /**< Medial */
|
||||
UTF8PROC_DECOMP_TYPE_FINAL = 5, /**< Final */
|
||||
UTF8PROC_DECOMP_TYPE_ISOLATED = 6, /**< Isolated */
|
||||
UTF8PROC_DECOMP_TYPE_CIRCLE = 7, /**< Circle */
|
||||
UTF8PROC_DECOMP_TYPE_SUPER = 8, /**< Super */
|
||||
UTF8PROC_DECOMP_TYPE_SUB = 9, /**< Sub */
|
||||
UTF8PROC_DECOMP_TYPE_VERTICAL = 10, /**< Vertical */
|
||||
UTF8PROC_DECOMP_TYPE_WIDE = 11, /**< Wide */
|
||||
UTF8PROC_DECOMP_TYPE_NARROW = 12, /**< Narrow */
|
||||
UTF8PROC_DECOMP_TYPE_SMALL = 13, /**< Small */
|
||||
UTF8PROC_DECOMP_TYPE_SQUARE = 14, /**< Square */
|
||||
UTF8PROC_DECOMP_TYPE_FRACTION = 15, /**< Fraction */
|
||||
UTF8PROC_DECOMP_TYPE_COMPAT = 16, /**< Compat */
|
||||
} utf8proc_decomp_type_t;
|
||||
|
||||
/** Boundclass property. (TR29) */
|
||||
typedef enum {
|
||||
UTF8PROC_BOUNDCLASS_START = 0, /**< Start */
|
||||
UTF8PROC_BOUNDCLASS_OTHER = 1, /**< Other */
|
||||
UTF8PROC_BOUNDCLASS_CR = 2, /**< Cr */
|
||||
UTF8PROC_BOUNDCLASS_LF = 3, /**< Lf */
|
||||
UTF8PROC_BOUNDCLASS_CONTROL = 4, /**< Control */
|
||||
UTF8PROC_BOUNDCLASS_EXTEND = 5, /**< Extend */
|
||||
UTF8PROC_BOUNDCLASS_L = 6, /**< L */
|
||||
UTF8PROC_BOUNDCLASS_V = 7, /**< V */
|
||||
UTF8PROC_BOUNDCLASS_T = 8, /**< T */
|
||||
UTF8PROC_BOUNDCLASS_LV = 9, /**< Lv */
|
||||
UTF8PROC_BOUNDCLASS_LVT = 10, /**< Lvt */
|
||||
UTF8PROC_BOUNDCLASS_REGIONAL_INDICATOR = 11, /**< Regional indicator */
|
||||
UTF8PROC_BOUNDCLASS_SPACINGMARK = 12, /**< Spacingmark */
|
||||
UTF8PROC_BOUNDCLASS_PREPEND = 13, /**< Prepend */
|
||||
UTF8PROC_BOUNDCLASS_ZWJ = 14, /**< Zero Width Joiner */
|
||||
UTF8PROC_BOUNDCLASS_E_BASE = 15, /**< Emoji Base */
|
||||
UTF8PROC_BOUNDCLASS_E_MODIFIER = 16, /**< Emoji Modifier */
|
||||
UTF8PROC_BOUNDCLASS_GLUE_AFTER_ZWJ = 17, /**< Glue_After_ZWJ */
|
||||
UTF8PROC_BOUNDCLASS_E_BASE_GAZ = 18, /**< E_BASE + GLUE_AFTER_ZJW */
|
||||
} utf8proc_boundclass_t;
|
||||
|
||||
/**
|
||||
* Function pointer type passed to @ref utf8proc_map_custom and
|
||||
* @ref utf8proc_decompose_custom, which is used to specify a user-defined
|
||||
* mapping of codepoints to be applied in conjunction with other mappings.
|
||||
*/
|
||||
typedef utf8proc_int32_t (*utf8proc_custom_func)(utf8proc_int32_t codepoint, void *data);
|
||||
|
||||
/**
|
||||
* Array containing the byte lengths of a UTF-8 encoded codepoint based
|
||||
* on the first byte.
|
||||
*/
|
||||
UTF8PROC_DLLEXPORT extern const utf8proc_int8_t utf8proc_utf8class[256];
|
||||
|
||||
/**
|
||||
* Returns the utf8proc API version as a string MAJOR.MINOR.PATCH
|
||||
* (http://semver.org format), possibly with a "-dev" suffix for
|
||||
* development versions.
|
||||
*/
|
||||
UTF8PROC_DLLEXPORT const char* utf8proc_version(void);
|
||||
|
||||
/**
|
||||
* Returns an informative error string for the given utf8proc error code
|
||||
* (e.g. the error codes returned by @ref utf8proc_map).
|
||||
*/
|
||||
UTF8PROC_DLLEXPORT const char* utf8proc_errmsg(utf8proc_ssize_t errcode);
|
||||
|
||||
/**
|
||||
* Reads a single codepoint from the UTF-8 sequence being pointed to by `str`.
|
||||
* The maximum number of bytes read is `strlen`, unless `strlen` is
|
||||
* negative (in which case up to 4 bytes are read).
|
||||
*
|
||||
* If a valid codepoint could be read, it is stored in the variable
|
||||
* pointed to by `codepoint_ref`, otherwise that variable will be set to -1.
|
||||
* In case of success, the number of bytes read is returned; otherwise, a
|
||||
* negative error code is returned.
|
||||
*/
|
||||
UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_iterate(
|
||||
const utf8proc_uint8_t *str,
|
||||
utf8proc_ssize_t strlen,
|
||||
utf8proc_int32_t *codepoint_ref);
|
||||
|
||||
/**
|
||||
* Check if a codepoint is valid (regardless of whether it has been
|
||||
* assigned a value by the current Unicode standard).
|
||||
*
|
||||
* @return 1 if the given `codepoint` is valid and otherwise return 0.
|
||||
*/
|
||||
UTF8PROC_DLLEXPORT utf8proc_bool utf8proc_codepoint_valid(utf8proc_int32_t codepoint);
|
||||
|
||||
/**
|
||||
* Encodes the codepoint as an UTF-8 string in the byte array pointed
|
||||
* to by `dst`. This array must be at least 4 bytes long.
|
||||
*
|
||||
* In case of success the number of bytes written is returned, and
|
||||
* otherwise 0 is returned.
|
||||
*
|
||||
* This function does not check whether `codepoint` is valid Unicode.
|
||||
*/
|
||||
UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_encode_char(utf8proc_int32_t codepoint, utf8proc_uint8_t *dst);
|
||||
|
||||
/**
|
||||
* Look up the properties for a given codepoint.
|
||||
*
|
||||
* @param codepoint The Unicode codepoint.
|
||||
*
|
||||
* @returns
|
||||
* A pointer to a (constant) struct containing information about
|
||||
* the codepoint.
|
||||
* @par
|
||||
* If the codepoint is unassigned or invalid, a pointer to a special struct is
|
||||
* returned in which `category` is 0 (@ref UTF8PROC_CATEGORY_CN).
|
||||
*/
|
||||
UTF8PROC_DLLEXPORT const utf8proc_property_t* utf8proc_get_property(utf8proc_int32_t codepoint);
|
||||
|
||||
/** Decompose a codepoint into an array of codepoints.
|
||||
*
|
||||
* @param codepoint the codepoint.
|
||||
* @param dst the destination buffer.
|
||||
* @param bufsize the size of the destination buffer.
|
||||
* @param options one or more of the following flags:
|
||||
* - @ref UTF8PROC_REJECTNA - return an error `codepoint` is unassigned
|
||||
* - @ref UTF8PROC_IGNORE - strip "default ignorable" codepoints
|
||||
* - @ref UTF8PROC_CASEFOLD - apply Unicode casefolding
|
||||
* - @ref UTF8PROC_COMPAT - replace certain codepoints with their
|
||||
* compatibility decomposition
|
||||
* - @ref UTF8PROC_CHARBOUND - insert 0xFF bytes before each grapheme cluster
|
||||
* - @ref UTF8PROC_LUMP - lump certain different codepoints together
|
||||
* - @ref UTF8PROC_STRIPMARK - remove all character marks
|
||||
* - @ref UTF8PROC_STRIPNA - remove unassigned codepoints
|
||||
* @param last_boundclass
|
||||
* Pointer to an integer variable containing
|
||||
* the previous codepoint's boundary class if the @ref UTF8PROC_CHARBOUND
|
||||
* option is used. Otherwise, this parameter is ignored.
|
||||
*
|
||||
* @return
|
||||
* In case of success, the number of codepoints written is returned; in case
|
||||
* of an error, a negative error code is returned (@ref utf8proc_errmsg).
|
||||
* @par
|
||||
* If the number of written codepoints would be bigger than `bufsize`, the
|
||||
* required buffer size is returned, while the buffer will be overwritten with
|
||||
* undefined data.
|
||||
*/
|
||||
UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_decompose_char(
|
||||
utf8proc_int32_t codepoint, utf8proc_int32_t *dst, utf8proc_ssize_t bufsize,
|
||||
utf8proc_option_t options, int *last_boundclass
|
||||
);
|
||||
|
||||
/**
|
||||
* The same as @ref utf8proc_decompose_char, but acts on a whole UTF-8
|
||||
* string and orders the decomposed sequences correctly.
|
||||
*
|
||||
* If the @ref UTF8PROC_NULLTERM flag in `options` is set, processing
|
||||
* will be stopped, when a NULL byte is encounted, otherwise `strlen`
|
||||
* bytes are processed. The result (in the form of 32-bit unicode
|
||||
* codepoints) is written into the buffer being pointed to by
|
||||
* `buffer` (which must contain at least `bufsize` entries). In case of
|
||||
* success, the number of codepoints written is returned; in case of an
|
||||
* error, a negative error code is returned (@ref utf8proc_errmsg).
|
||||
* See @ref utf8proc_decompose_custom to supply additional transformations.
|
||||
*
|
||||
* If the number of written codepoints would be bigger than `bufsize`, the
|
||||
* required buffer size is returned, while the buffer will be overwritten with
|
||||
* undefined data.
|
||||
*/
|
||||
UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_decompose(
|
||||
const utf8proc_uint8_t *str, utf8proc_ssize_t strlen,
|
||||
utf8proc_int32_t *buffer, utf8proc_ssize_t bufsize, utf8proc_option_t options
|
||||
);
|
||||
|
||||
/**
|
||||
* The same as @ref utf8proc_decompose, but also takes a `custom_func` mapping function
|
||||
* that is called on each codepoint in `str` before any other transformations
|
||||
* (along with a `custom_data` pointer that is passed through to `custom_func`).
|
||||
* The `custom_func` argument is ignored if it is `NULL`. See also @ref utf8proc_map_custom.
|
||||
*/
|
||||
UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_decompose_custom(
|
||||
const utf8proc_uint8_t *str, utf8proc_ssize_t strlen,
|
||||
utf8proc_int32_t *buffer, utf8proc_ssize_t bufsize, utf8proc_option_t options,
|
||||
utf8proc_custom_func custom_func, void *custom_data
|
||||
);
|
||||
|
||||
/**
|
||||
* Normalizes the sequence of `length` codepoints pointed to by `buffer`
|
||||
* in-place (i.e., the result is also stored in `buffer`).
|
||||
*
|
||||
* @param buffer the (native-endian UTF-32) unicode codepoints to re-encode.
|
||||
* @param length the length (in codepoints) of the buffer.
|
||||
* @param options a bitwise or (`|`) of one or more of the following flags:
|
||||
* - @ref UTF8PROC_NLF2LS - convert LF, CRLF, CR and NEL into LS
|
||||
* - @ref UTF8PROC_NLF2PS - convert LF, CRLF, CR and NEL into PS
|
||||
* - @ref UTF8PROC_NLF2LF - convert LF, CRLF, CR and NEL into LF
|
||||
* - @ref UTF8PROC_STRIPCC - strip or convert all non-affected control characters
|
||||
* - @ref UTF8PROC_COMPOSE - try to combine decomposed codepoints into composite
|
||||
* codepoints
|
||||
* - @ref UTF8PROC_STABLE - prohibit combining characters that would violate
|
||||
* the unicode versioning stability
|
||||
*
|
||||
* @return
|
||||
* In case of success, the length (in codepoints) of the normalized UTF-32 string is
|
||||
* returned; otherwise, a negative error code is returned (@ref utf8proc_errmsg).
|
||||
*
|
||||
* @warning The entries of the array pointed to by `str` have to be in the
|
||||
* range `0x0000` to `0x10FFFF`. Otherwise, the program might crash!
|
||||
*/
|
||||
UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_normalize_utf32(
|
||||
utf8proc_int32_t *buffer,
|
||||
utf8proc_ssize_t length,
|
||||
utf8proc_option_t options);
|
||||
|
||||
/**
|
||||
* Reencodes the sequence of `length` codepoints pointed to by `buffer`
|
||||
* UTF-8 data in-place (i.e., the result is also stored in `buffer`).
|
||||
* Can optionally normalize the UTF-32 sequence prior to UTF-8 conversion.
|
||||
*
|
||||
* @param buffer the (native-endian UTF-32) unicode codepoints to re-encode.
|
||||
* @param length the length (in codepoints) of the buffer.
|
||||
* @param options a bitwise or (`|`) of one or more of the following flags:
|
||||
* - @ref UTF8PROC_NLF2LS - convert LF, CRLF, CR and NEL into LS
|
||||
* - @ref UTF8PROC_NLF2PS - convert LF, CRLF, CR and NEL into PS
|
||||
* - @ref UTF8PROC_NLF2LF - convert LF, CRLF, CR and NEL into LF
|
||||
* - @ref UTF8PROC_STRIPCC - strip or convert all non-affected control characters
|
||||
* - @ref UTF8PROC_COMPOSE - try to combine decomposed codepoints into composite
|
||||
* codepoints
|
||||
* - @ref UTF8PROC_STABLE - prohibit combining characters that would violate
|
||||
* the unicode versioning stability
|
||||
* - @ref UTF8PROC_CHARBOUND - insert 0xFF bytes before each grapheme cluster
|
||||
*
|
||||
* @return
|
||||
* In case of success, the length (in bytes) of the resulting nul-terminated
|
||||
* UTF-8 string is returned; otherwise, a negative error code is returned
|
||||
* (@ref utf8proc_errmsg).
|
||||
*
|
||||
* @warning The amount of free space pointed to by `buffer` must
|
||||
* exceed the amount of the input data by one byte, and the
|
||||
* entries of the array pointed to by `str` have to be in the
|
||||
* range `0x0000` to `0x10FFFF`. Otherwise, the program might crash!
|
||||
*/
|
||||
UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_reencode(
|
||||
utf8proc_int32_t *buffer,
|
||||
utf8proc_ssize_t length,
|
||||
utf8proc_option_t options);
|
||||
|
||||
/**
|
||||
* Given a pair of consecutive codepoints, return whether a grapheme break is
|
||||
* permitted between them (as defined by the extended grapheme clusters in UAX#29).
|
||||
*
|
||||
* @param state Beginning with Version 29 (Unicode 9.0.0), this algorithm requires
|
||||
* state to break graphemes. This state can be passed in as a pointer
|
||||
* in the `state` argument and should initially be set to 0. If the
|
||||
* state is not passed in (i.e. a null pointer is passed), UAX#29 rules
|
||||
* GB10/12/13 which require this state will not be applied, essentially
|
||||
* matching the rules in Unicode 8.0.0.
|
||||
*
|
||||
* @warning If the state parameter is used, `utf8proc_grapheme_break_stateful` must
|
||||
* be called IN ORDER on ALL potential breaks in a string.
|
||||
*/
|
||||
UTF8PROC_DLLEXPORT utf8proc_bool utf8proc_grapheme_break_stateful(
|
||||
utf8proc_int32_t codepoint1,
|
||||
utf8proc_int32_t codepoint2,
|
||||
utf8proc_int32_t *state);
|
||||
|
||||
/**
|
||||
* Same as @ref utf8proc_grapheme_break_stateful, except without support for the
|
||||
* Unicode 9 additions to the algorithm. Supported for legacy reasons.
|
||||
*/
|
||||
UTF8PROC_DLLEXPORT utf8proc_bool utf8proc_grapheme_break(utf8proc_int32_t codepoint1, utf8proc_int32_t codepoint2);
|
||||
|
||||
|
||||
/**
|
||||
* Given a codepoint `c`, return the codepoint of the corresponding
|
||||
* lower-case character, if any; otherwise (if there is no lower-case
|
||||
* variant, or if `c` is not a valid codepoint) return `c`.
|
||||
*/
|
||||
UTF8PROC_DLLEXPORT utf8proc_int32_t utf8proc_tolower(utf8proc_int32_t c);
|
||||
|
||||
/**
|
||||
* Given a codepoint `c`, return the codepoint of the corresponding
|
||||
* upper-case character, if any; otherwise (if there is no upper-case
|
||||
* variant, or if `c` is not a valid codepoint) return `c`.
|
||||
*/
|
||||
UTF8PROC_DLLEXPORT utf8proc_int32_t utf8proc_toupper(utf8proc_int32_t c);
|
||||
|
||||
/**
|
||||
* Given a codepoint `c`, return the codepoint of the corresponding
|
||||
* title-case character, if any; otherwise (if there is no title-case
|
||||
* variant, or if `c` is not a valid codepoint) return `c`.
|
||||
*/
|
||||
UTF8PROC_DLLEXPORT utf8proc_int32_t utf8proc_totitle(utf8proc_int32_t c);
|
||||
|
||||
/**
|
||||
* Given a codepoint, return a character width analogous to `wcwidth(codepoint)`,
|
||||
* except that a width of 0 is returned for non-printable codepoints
|
||||
* instead of -1 as in `wcwidth`.
|
||||
*
|
||||
* @note
|
||||
* If you want to check for particular types of non-printable characters,
|
||||
* (analogous to `isprint` or `iscntrl`), use @ref utf8proc_category. */
|
||||
UTF8PROC_DLLEXPORT int utf8proc_charwidth(utf8proc_int32_t codepoint);
|
||||
|
||||
/**
|
||||
* Return the Unicode category for the codepoint (one of the
|
||||
* @ref utf8proc_category_t constants.)
|
||||
*/
|
||||
UTF8PROC_DLLEXPORT utf8proc_category_t utf8proc_category(utf8proc_int32_t codepoint);
|
||||
|
||||
/**
|
||||
* Return the two-letter (nul-terminated) Unicode category string for
|
||||
* the codepoint (e.g. `"Lu"` or `"Co"`).
|
||||
*/
|
||||
UTF8PROC_DLLEXPORT const char* utf8proc_category_string(utf8proc_int32_t codepoint);
|
||||
|
||||
/**
|
||||
* Maps the given UTF-8 string pointed to by `str` to a new UTF-8
|
||||
* string, allocated dynamically by `malloc` and returned via `dstptr`.
|
||||
*
|
||||
* If the @ref UTF8PROC_NULLTERM flag in the `options` field is set,
|
||||
* the length is determined by a NULL terminator, otherwise the
|
||||
* parameter `strlen` is evaluated to determine the string length, but
|
||||
* in any case the result will be NULL terminated (though it might
|
||||
* contain NULL characters with the string if `str` contained NULL
|
||||
* characters). Other flags in the `options` field are passed to the
|
||||
* functions defined above, and regarded as described. See also
|
||||
* @ref utfproc_map_custom to supply a custom codepoint transformation.
|
||||
*
|
||||
* In case of success the length of the new string is returned,
|
||||
* otherwise a negative error code is returned.
|
||||
*
|
||||
* @note The memory of the new UTF-8 string will have been allocated
|
||||
* with `malloc`, and should therefore be deallocated with `free`.
|
||||
*/
|
||||
UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_map(
|
||||
const utf8proc_uint8_t *str, utf8proc_ssize_t strlen, utf8proc_uint8_t **dstptr, utf8proc_option_t options
|
||||
);
|
||||
|
||||
/**
|
||||
* Like @ref utf8proc_map, but also takes a `custom_func` mapping function
|
||||
* that is called on each codepoint in `str` before any other transformations
|
||||
* (along with a `custom_data` pointer that is passed through to `custom_func`).
|
||||
* The `custom_func` argument is ignored if it is `NULL`.
|
||||
*/
|
||||
UTF8PROC_DLLEXPORT utf8proc_ssize_t utf8proc_map_custom(
|
||||
const utf8proc_uint8_t *str, utf8proc_ssize_t strlen, utf8proc_uint8_t **dstptr, utf8proc_option_t options,
|
||||
utf8proc_custom_func custom_func, void *custom_data
|
||||
);
|
||||
|
||||
/** @name Unicode normalization
|
||||
*
|
||||
* Returns a pointer to newly allocated memory of a NFD, NFC, NFKD, NFKC or
|
||||
* NFKC_Casefold normalized version of the null-terminated string `str`. These
|
||||
* are shortcuts to calling @ref utf8proc_map with @ref UTF8PROC_NULLTERM
|
||||
* combined with @ref UTF8PROC_STABLE and flags indicating the normalization.
|
||||
*/
|
||||
/** @{ */
|
||||
/** NFD normalization (@ref UTF8PROC_DECOMPOSE). */
|
||||
UTF8PROC_DLLEXPORT utf8proc_uint8_t* utf8proc_NFD(const utf8proc_uint8_t *str);
|
||||
|
||||
/** NFC normalization (@ref UTF8PROC_COMPOSE). */
|
||||
UTF8PROC_DLLEXPORT utf8proc_uint8_t* utf8proc_NFC(const utf8proc_uint8_t *str);
|
||||
|
||||
/** NFKD normalization (@ref UTF8PROC_DECOMPOSE and @ref UTF8PROC_COMPAT). */
|
||||
UTF8PROC_DLLEXPORT utf8proc_uint8_t* utf8proc_NFKD(const utf8proc_uint8_t *str);
|
||||
|
||||
/** NFKC normalization (@ref UTF8PROC_COMPOSE and @ref UTF8PROC_COMPAT). */
|
||||
UTF8PROC_DLLEXPORT utf8proc_uint8_t* utf8proc_NFKC(const utf8proc_uint8_t *str);
|
||||
|
||||
/**
|
||||
* NFKC_Casefold normalization (@ref UTF8PROC_COMPOSE and @ref UTF8PROC_COMPAT
|
||||
* and @ref UTF8PROC_CASEFOLD and @ref UTF8PROC_IGNORE).
|
||||
**/
|
||||
UTF8PROC_DLLEXPORT utf8proc_uint8_t* utf8proc_NFKC_Casefold(const utf8proc_uint8_t *str);
|
||||
|
||||
/** @} */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user