update from v1.4.15 to v1.4.16

Signed-off-by: zhouhaifeng <kutcher.zhou@huawei.com>
This commit is contained in:
zhouhaifeng
2023-03-22 21:41:47 +08:00
parent d153aaad1e
commit 02794a1cb5
101 changed files with 20437 additions and 4227 deletions
+3 -1
View File
@@ -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
+14 -3
View File
@@ -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"
+1
View File
@@ -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)
+34
View File
@@ -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.
+19
View File
@@ -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 &gt;AS IS&gt;. USE ENTIRELY AT YOUR OWN RISK."
desc=""/>
</licensematcher>
<licensematcher name="CC0 1.0 Universal" desc="">
<licensetext name="
Written in 2014-2016 by Eric Biggers &gt;ebiggers3@gmail.com&gt;
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 &gt;CC0&gt;).
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 &gt;http://creativecommons.org/publicdomain/zero/1.0/&gt;."
desc=""/>
</licensematcher>
</licensematcherlist>
</oatconfig>
</configuration>
+1 -1
View File
@@ -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"
+19 -23
View File
@@ -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
==================================================================================================================
[![Join ejdb2 telegram](https://img.shields.io/badge/join-ejdb2%20telegram-0088cc.svg)](https://t.me/ejdb2)
[![license](https://img.shields.io/github/license/Softmotions/ejdb.svg)](https://github.com/Softmotions/iowow/blob/master/LICENSE)
![Maintained](https://img.shields.io/maintenance/yes/2022.svg)
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
[![Presentation](https://iowow.io/articles/iowow-presentation-cover-small.png)](https://iowow.io/articles/intro/)
[![IWKV Presentation](https://iowow.softmotions.com/articles/iowow-presentation-cover-small.png)](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
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
+2
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+3 -4
View File
@@ -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
+4 -1
View 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;
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+1053
View File
File diff suppressed because it is too large Load Diff
+597
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+888
View File
@@ -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
+90
View File
@@ -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
+20
View File
@@ -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 ()
+36
View File
@@ -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"
}
}
}
}
}
+36
View File
@@ -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"
}
}
}
}
}
+36
View File
@@ -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"
}
}
}
}
}
+36
View File
@@ -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"
}
}
}
}
}
+3
View File
@@ -0,0 +1,3 @@
{
"empty": ""
}
+3
View File
@@ -0,0 +1,3 @@
{
"empty": ""
}
+100
View File
@@ -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"
}
}
}
+100
View File
@@ -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"
}
}
}
+17
View File
@@ -0,0 +1,17 @@
{
"foo": "b\"ar",
"num1": 1223,
"n\"um2": 10.1226222,
"list": [
3,
2.1,
1,
"one",
"two",
{},
{
"z": false,
"t": true
}
]
}
+1
View File
@@ -0,0 +1 @@
{"foo": "b\"ar", "num1":1223,"n\"um2":10.1226222, "list":[3,2.1,1,"one", "two", {}, {"z":false, "t":true}]}
+906
View File
@@ -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
View File
@@ -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()
+1 -1
View File
@@ -111,7 +111,7 @@ finish:
return rc;
}
int main() {
int main(void) {
iwrc rc = run();
if (rc) {
iwlog_ecode_error3(rc);
+1 -1
View File
@@ -106,7 +106,7 @@ finish:
return rc;
}
int main() {
int main(void) {
iwrc rc = run();
if (rc) {
iwlog_ecode_error3(rc);
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
-12
View File
@@ -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
View File
@@ -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 */
+3 -1
View File
@@ -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);
-4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
+3 -3
View File
@@ -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;
}
+1
View File
@@ -0,0 +1 @@
Based on https://github.com/jserv/cregex library (BSD 2-Clause "Simplified" License)
+339
View File
@@ -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
View File
@@ -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
+64
View File
@@ -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;
}
+21
View File
@@ -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
View File
@@ -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);
}
+14
View File
@@ -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()
+66
View File
@@ -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
View File
@@ -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
View File
@@ -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) {
+18
View File
@@ -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.
*
+789
View File
@@ -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);
}
+369
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+307
View File
@@ -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);
}
+173
View File
@@ -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
View File
@@ -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
View File
@@ -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)));
-994
View File
@@ -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 */
-75
View File
@@ -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_ */
-226
View File
@@ -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]);
}
}
-62
View File
@@ -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
-411
View File
@@ -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;
}
-101
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+6
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
-629
View File
@@ -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 */
+3 -3
View File
@@ -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
}
+7 -9
View File
@@ -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()
+54 -3
View File
@@ -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();
}
+18 -9
View File
@@ -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();
}
+329
View File
@@ -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;
}
+733
View File
@@ -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