Files
mlib/m-dict.h
T
2020-10-23 21:50:57 +02:00

1996 lines
145 KiB
C

/*
* M*LIB - DICTIONARY Module
*
* Copyright (c) 2017-2020, Patrick Pelissier
* 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.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 THE REGENTS AND CONTRIBUTORS 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.
*/
#ifndef MSTARLIB_DICT_H
#define MSTARLIB_DICT_H
#include "m-list.h"
#include "m-array.h"
#include "m-tuple.h"
/* Define a dictionary associating the key key_type to the value value_type and its associated functions.
USAGE:
DICT_DEF2(name, key_type, key_oplist, value_type, value_oplist)
OR
DICT_DEF2(name, key_type, value_type)
*/
#define DICT_DEF2(name, key_type, ...) \
M_BEGIN_PROTECTED_CODE \
DICTI_DEF2_P1(M_IF_NARGS_EQ1(__VA_ARGS__) \
((name, key_type, M_GLOBAL_OPLIST_OR_DEF(key_type)(), __VA_ARGS__, M_GLOBAL_OPLIST_OR_DEF(__VA_ARGS__)(),M_C(name, _t), M_C(name, _it_t), M_C(name, _itref_t) ), \
(name, key_type, __VA_ARGS__,M_C(name, _t), M_C(name, _it_t), M_C(name, _itref_t) ))) \
M_END_PROTECTED_CODE
/* Define a dictionary associating the key key_type to the value value_type and its associated functions.
as the given name name_t with its associated functions.
USAGE:
DICT_DEF2_AS(name, name_t, it_t, itref_t, key_type, key_oplist, value_type, value_oplist)
OR
DICT_DEF2_AS(name, name_t, it_t, itref_t, key_type, value_type)
*/
#define DICT_DEF2_AS(name, name_it, it_t, itref_t, key_type, ...) \
M_BEGIN_PROTECTED_CODE \
DICTI_DEF2_P1(M_IF_NARGS_EQ1(__VA_ARGS__) \
((name, key_type, M_GLOBAL_OPLIST_OR_DEF(key_type)(), __VA_ARGS__, M_GLOBAL_OPLIST_OR_DEF(__VA_ARGS__)(), name_t, it_t, itref_t ), \
(name, key_type, __VA_ARGS__, name_t, it_t, itref_t ) )) \
M_END_PROTECTED_CODE
/* Define a dictionary asssociating the key key_type to the value value_type and its associated functions.
It stores the computed hash value, avoiding the need of recomputing it but increasing memory
consumption.
USAGE:
DICT_STOREHASH_DEF2(name, key_type[, key_oplist], value_type[, value_oplist])
OR
DICT_STOREHASH_DEF2(name, key_type[, key_oplist], value_type[, value_oplist])
*/
#define DICT_STOREHASH_DEF2(name, key_type, ...) \
M_BEGIN_PROTECTED_CODE \
DICTI_SHASH_DEF2_P1(M_IF_NARGS_EQ1(__VA_ARGS__) \
((name, key_type, M_GLOBAL_OPLIST_OR_DEF(key_type)(), __VA_ARGS__, M_GLOBAL_OPLIST_OR_DEF(__VA_ARGS__)(),M_C(name, _t), M_C(name, _it_t), M_C(name, _itref_t) ), \
(name, key_type, __VA_ARGS__,M_C(name, _t), M_C(name, _it_t), M_C(name, _itref_t) ))) \
M_END_PROTECTED_CODE
/* Define a dictionary asssociating the key key_type to the value value_type and its associated functions.
as the given name name_t with its associated functions.
It stores the computed hash value, avoiding the need of recomputing it but increasing memory
consumption.
USAGE:
DICT_STOREHASH_DEF2_AS(name, name_it, it_t, itref_t, key_type[, key_oplist], value_type[, value_oplist])
OR
DICT_STOREHASH_DEF2_AS(name, name_it, it_t, itref_t, key_type[, key_oplist], value_type[, value_oplist])
*/
#define DICT_STOREHASH_DEF2_AS(name, name_it, it_t, itref_t, key_type, ...) \
M_BEGIN_PROTECTED_CODE \
DICTI_SHASH_DEF2_P1(M_IF_NARGS_EQ1(__VA_ARGS__) \
((name, key_type, M_GLOBAL_OPLIST_OR_DEF(key_type)(), __VA_ARGS__, M_GLOBAL_OPLIST_OR_DEF(__VA_ARGS__)(), name_it, it_t, itref_t ), \
(name, key_type, __VA_ARGS__, name_it, it_t, itref_t ))) \
M_END_PROTECTED_CODE
/* Define a dictionary associating the key key_type to the value value_type
with an Open Addressing implementation and its associated functions.
KEY_OPLIST needs the operators OOR_EQUAL & OOR_SET.
USAGE:
DICT_OA_DEF2(name, key_type, key_oplist, value_type, value_oplist)
OR
DICT_OA_DEF2(name, key_type, value_type)
*/
#define DICT_OA_DEF2(name, key_type, ...) \
M_BEGIN_PROTECTED_CODE \
DICTI_OA_DEF_P1(M_IF_NARGS_EQ1(__VA_ARGS__) \
((name, key_type, M_GLOBAL_OPLIST_OR_DEF(key_type)(), __VA_ARGS__, M_GLOBAL_OPLIST_OR_DEF(__VA_ARGS__)(),M_C(name, _t), M_C(name, _it_t), M_C(name, _itref_t) ), \
(name, key_type, __VA_ARGS__,M_C(name, _t), M_C(name, _it_t), M_C(name, _itref_t) ))) \
M_END_PROTECTED_CODE
/* Define a dictionary associating the key key_type to the value value_type
with an Open Addressing implementation and its associated functions.
as the given name name_t with its associated functions.
KEY_OPLIST needs the operators OOR_EQUAL & OOR_SET.
USAGE:
DICT_OA_DEF2_AS(name, name_it, it_t, itref_t, key_type, key_oplist, value_type, value_oplist)
OR
DICT_OA_DEF2_AS(name, name_it, it_t, itref_t, key_type, value_type)
*/
#define DICT_OA_DEF2_AS(name, name_it, it_t, itref_t, key_type, ...) \
M_BEGIN_PROTECTED_CODE \
DICTI_OA_DEF_P1(M_IF_NARGS_EQ1(__VA_ARGS__) \
((name, key_type, M_GLOBAL_OPLIST_OR_DEF(key_type)(), __VA_ARGS__, M_GLOBAL_OPLIST_OR_DEF(__VA_ARGS__)(), name_it, it_t, itref_t ), \
(name, key_type, __VA_ARGS__, name_it, it_t, itref_t ))) \
M_END_PROTECTED_CODE
/* Define a set of the key key_type and its associated functions.
The set is unordered.
USAGE: DICT_SET_DEF(name, key_type[, key_oplist])
*/
#define DICT_SET_DEF(name, ...) \
M_BEGIN_PROTECTED_CODE \
DICTI_SET_DEF_P1(M_IF_NARGS_EQ1(__VA_ARGS__) \
((name, __VA_ARGS__, M_GLOBAL_OPLIST_OR_DEF(__VA_ARGS__)(),M_C(name, _t), M_C(name, _it_t), M_C(name, _itref_t)), \
(name, __VA_ARGS__,M_C(name, _t), M_C(name, _it_t), M_C(name, _itref_t)))) \
M_END_PROTECTED_CODE
/* Define a set of the key key_type and its associated functions.
as the given name name_t with its associated functions.
The set is unordered.
USAGE: DICT_SET_DEF_AS(name, name_it, it_t, key_type[, key_oplist])
*/
#define DICT_SET_DEF_AS(name, name_it, it_t, ...) \
M_BEGIN_PROTECTED_CODE \
DICTI_SET_DEF_P1(M_IF_NARGS_EQ1(__VA_ARGS__) \
((name, __VA_ARGS__, M_GLOBAL_OPLIST_OR_DEF(__VA_ARGS__)(), name_it, it_t, M_C(name, _itref_ct) ), \
(name, __VA_ARGS__, name_it, it_t, M_C(name, _itref_ct) ))) \
M_END_PROTECTED_CODE
/* Define a set of the key key_type
with an Open Addressing implementation and its associated functions.
The set is unordered.
USAGE: DICT_OASET_DEF(name, key_type[, key_oplist])
*/
#define DICT_OASET_DEF(name, ...) \
M_BEGIN_PROTECTED_CODE \
DICTI_OASET_DEF_P1(M_IF_NARGS_EQ1(__VA_ARGS__) \
((name, __VA_ARGS__, M_GLOBAL_OPLIST_OR_DEF(__VA_ARGS__)(), M_C(name, _t), M_C(name, _it_t), M_C(name, _itref_t) ), \
(name, __VA_ARGS__,M_C(name, _t), M_C(name, _it_t), M_C(name, _itref_t) ))) \
M_END_PROTECTED_CODE
/* Define a set of the key key_type
with an Open Addressing implementation and its associated functions.
as the given name name_t with its associated functions.
The set is unordered.
USAGE: DICT_OASET_DEF_AS(name, name_it, it_t, key_type[, key_oplist])
*/
#define DICT_OASET_DEF_AS(name, name_it, it_t, ...) \
M_BEGIN_PROTECTED_CODE \
DICTI_OASET_DEF_P1(M_IF_NARGS_EQ1(__VA_ARGS__) \
((name, __VA_ARGS__, M_GLOBAL_OPLIST_OR_DEF(__VA_ARGS__)(), name_it, it_t, M_C(name, _itref_ct) ), \
(name, __VA_ARGS__, name_it, it_t, M_C(name, _itref_ct) ))) \
M_END_PROTECTED_CODE
/* Define the oplist of a dictionnary (DICT_DEF2, DICT_STOREHASH_DEF2 or DICT_OA_DEF2).
USAGE:
DICT_OPLIST(name, oplist of the key type, oplist of the value type)
OR
DICT_OPLIST(name)
*/
#define DICT_OPLIST(...) \
M_IF_NARGS_EQ1(__VA_ARGS__) \
(DICTI_OPLIST_P1((__VA_ARGS__, M_DEFAULT_OPLIST, M_DEFAULT_OPLIST )), \
DICTI_OPLIST_P1((__VA_ARGS__ )))
/* Define the oplist of a dictionnary (DICT_SET_DEF).
USAGE: DICT_SET_OPLIST(name[, oplist of the key type]) */
#define DICT_SET_OPLIST(...) \
M_IF_NARGS_EQ1(__VA_ARGS__) \
(DICTI_SET_OPLIST_P1((__VA_ARGS__, M_DEFAULT_OPLIST)), \
DICTI_SET_OPLIST_P1((__VA_ARGS__ )))
/********************************** INTERNAL ************************************/
/* Define a dictionary from the key key_type to the value value_type.
It is defined as an array of singly linked list (each list
representing a bucket of items with the same hash value modulo the
current array size).
*/
/* Deferred evaluation for arg */
#define DICTI_DEF2_P1(arg) DICTI_DEF2_P2 arg
/* Validate the key oplist before going further */
#define DICTI_DEF2_P2(name, key_type, key_oplist, value_type, value_oplist, dict_t, dict_it_t, it_deref_t) \
M_IF_OPLIST(key_oplist)(DICTI_DEF2_P3, DICTI_DEF2_FAILURE)(name, key_type, key_oplist, value_type, value_oplist, dict_t, dict_it_t, it_deref_t)
/* Validate the value oplist before going further */
#define DICTI_DEF2_P3(name, key_type, key_oplist, value_type, value_oplist, dict_t, dict_it_t, it_deref_t) \
M_IF_OPLIST(value_oplist)(DICTI_DEF2_P4, DICTI_DEF2_FAILURE)(name, key_type, key_oplist, value_type, value_oplist, dict_t, dict_it_t, it_deref_t)
/* Stop processing with a compilation failure */
#define DICTI_DEF2_FAILURE(name, key_type, key_oplist, value_type, value_oplist, dict_t, dict_it_t, it_deref_t) \
M_STATIC_FAILURE(M_LIB_NOT_AN_OPLIST, "(DICT_DEF2): at least one of the given argument is not a valid oplist: " M_AS_STR(key_oplist) " / " M_AS_STR(value_oplist) )
#define DICTI_DEF2_P4(name, key_type, key_oplist, value_type, value_oplist, dict_t, dict_it_t, it_deref_t) \
\
M_CHECK_COMPATIBLE_OPLIST(name, 1, key_type, key_oplist) \
M_CHECK_COMPATIBLE_OPLIST(name, 2, value_type, value_oplist) \
\
TUPLE_DEF2(M_C(name, _pair), (key, key_type, key_oplist), (value, value_type, value_oplist)) \
\
DICTI_FUNC_DEF2_P5(name, key_type, key_oplist, value_type, value_oplist, \
M_C(name, _pair_ct), TUPLE_OPLIST(M_C(name, _pair), key_oplist, value_oplist), 0, 0, \
dict_t, dict_it_t, it_deref_t )
/* Define a dictionary with the key key_type to the value value_type.
which stores the computed hash value (avoiding the need of recomputing it).
It is defined as an array of singly linked list (each list
representing a bucket).
*/
/* Defered evaluation for arg */
#define DICTI_SHASH_DEF2_P1(arg) DICTI_SHASH_DEF2_P2 arg
/* Validate the key oplist before going further */
#define DICTI_SHASH_DEF2_P2(name, key_type, key_oplist, value_type, value_oplist, dict_t, dict_it_t, it_deref_t) \
M_IF_OPLIST(key_oplist)(DICTI_SHASH_DEF2_P3, DICTI_SHASH_DEF2_FAILURE)(name, key_type, key_oplist, value_type, value_oplist, dict_t, dict_it_t, it_deref_t)
/* Validate the value oplist before going further */
#define DICTI_SHASH_DEF2_P3(name, key_type, key_oplist, value_type, value_oplist, dict_t, dict_it_t, it_deref_t) \
M_IF_OPLIST(value_oplist)(DICTI_SHASH_DEF2_P4, DICTI_SHASH_DEF2_FAILURE)(name, key_type, key_oplist, value_type, value_oplist, dict_t, dict_it_t, it_deref_t)
/* Stop processing with a compilation failure */
#define DICTI_SHASH_DEF2_FAILURE(name, key_type, key_oplist, value_type, value_oplist, dict_t, dict_it_t, it_deref_t) \
M_STATIC_FAILURE(M_LIB_NOT_AN_OPLIST, "(DICT_STOREHASH_DEF2): at least one of the given argument is not a valid oplist: " M_AS_STR(key_oplist) " / " M_AS_STR(value_oplist) )
#define DICTI_SHASH_DEF2_P4(name, key_type, key_oplist, value_type, value_oplist, dict_t, dict_it_t, it_deref_t) \
\
M_CHECK_COMPATIBLE_OPLIST(name, 1, key_type, key_oplist) \
M_CHECK_COMPATIBLE_OPLIST(name, 2, value_type, value_oplist) \
\
TUPLE_DEF2(M_C(name, _pair), (hash, size_t, M_DEFAULT_OPLIST), (key, key_type, key_oplist), (value, value_type, value_oplist)) \
\
DICTI_FUNC_DEF2_P5(name, key_type, key_oplist, value_type, value_oplist, \
M_C(name, _pair_ct), TUPLE_OPLIST(M_C(name, _pair), M_DEFAULT_OPLIST, key_oplist, value_oplist), 0, 1, \
dict_t, dict_it_t, it_deref_t )
/* Define a set with the key key_type
It is defined as an array of singly linked list (each list
representing a bucket).
*/
#define DICTI_SET_DEF_P1(arg) DICTI_SET_DEF_P2 arg
/* Validate the key oplist before going further */
#define DICTI_SET_DEF_P2(name, key_type, key_oplist, dict_t, dict_it_t, it_deref_t) \
M_IF_OPLIST(key_oplist)(DICTI_SET_DEF_P4, DICTI_SET_DEF_FAILURE)(name, key_type, key_oplist, dict_t, dict_it_t, it_deref_t)
/* Stop processing with a compilation failure */
#define DICTI_SET_DEF_FAILURE(name, key_type, key_oplist, dict_t, dict_it_t, it_deref_t) \
M_STATIC_FAILURE(M_LIB_NOT_AN_OPLIST, "(DICT_SET_DEF): the given argument is not a valid oplist: " M_AS_STR(key_oplist) )
#define DICTI_SET_DEF_P4(name, key_type, key_oplist, dict_t, dict_it_t, it_deref_t) \
\
M_CHECK_COMPATIBLE_OPLIST(name, 1, key_type, key_oplist) \
\
TUPLE_DEF2(M_C(name, _pair), (key, key_type, key_oplist)) \
\
DICTI_FUNC_DEF2_P5(name, key_type, key_oplist, key_type, M_EMPTY_OPLIST, \
M_C(name, _pair_ct), TUPLE_OPLIST(M_C(name, _pair), key_oplist), 1, 0, \
dict_t, dict_it_t, it_deref_t)
/* Define the structure of a chained dictionnary for all kind of dictionnaries
* name: prefix of the container,
* key_type: type of the key
* key_oplist: oplist of the key
* value_type: type of the value (if not a SET)
* value_oplist: oplist of the value (if not a SET)
* pair_type: type of the pair (key, value)
* pair_oplist: oplist of the pair (key, value)
* isSet: is the container a SET (=1) or a MAP (=0)
* isStoreHash: is the computed hash stored in the bucker (=1) or not (=0)
* dict_t: name of the type to construct
* dict_it_t: name of the iterator within the dictionnary.
* it_deref_t: name of the type returned by an iterator
*/
#define DICTI_FUNC_DEF2_P5(name, key_type, key_oplist, value_type, value_oplist, pair_type, pair_oplist, isSet, isStoreHash, dict_t, dict_it_t, it_deref_t) \
\
/* NOTE: \
if isSet is true, all methods of value_oplist are NOP methods */ \
\
/* Define the list of buckets */ \
/* Use memory allocator for bucket if needed */ \
M_IF_METHOD(MEMPOOL, key_oplist) \
( \
LIST_DEF(M_C(name, _list_pair), pair_type, \
M_OPEXTEND(pair_oplist, MEMPOOL(M_GET_MEMPOOL key_oplist), MEMPOOL_LINKAGE(M_GET_MEMPOOL_LINKAGE key_oplist))) \
, \
LIST_DEF(M_C(name, _list_pair), pair_type, pair_oplist) \
) \
\
/* Define the array of list of buckets */ \
ARRAY_DEF(M_C(name, _array_list_pair), M_C(name, _list_pair_ct), \
LIST_OPLIST(M_C(name, _list_pair), pair_oplist)) \
\
/* Define chained dict type */ \
typedef struct M_C(name, _s) { \
size_t used, lower_limit, upper_limit; \
M_C(name, _array_list_pair_ct) table; \
} dict_t[1]; \
\
typedef struct M_C(name, _s) *M_C(name, _ptr); \
typedef const struct M_C(name, _s) *M_C(name, _srcptr); \
\
/* Define iterator type */ \
typedef struct M_C(name, _it_s) { \
M_C(name, _array_list_pair_it_ct) array_it; \
M_C(name, _list_pair_it_ct) list_it; \
} dict_it_t[1]; \
\
/* Define type returned by the _ref method of an iterator */ \
M_IF(isSet)( \
typedef key_type it_deref_t; \
, \
typedef struct M_C(name, _pair_s) it_deref_t; \
) \
\
/* Define internal types for oplist */ \
typedef dict_t M_C(name, _ct); \
typedef it_deref_t M_C(name, _subtype_ct); \
typedef key_type M_C(name, _key_ct); \
typedef value_type M_C(name, _value_ct); \
typedef dict_it_t M_C(name, _it_ct); \
\
static inline void \
M_C(name, _init)(dict_t map) \
{ \
assert (map != NULL); \
map->used = 0; \
M_C(name, _array_list_pair_init)(map->table); \
M_C(name, _array_list_pair_resize)(map->table, DICTI_INITIAL_SIZE); \
map->lower_limit = DICTI_LOWER_BOUND(DICTI_INITIAL_SIZE); \
map->upper_limit = DICTI_UPPER_BOUND(DICTI_INITIAL_SIZE); \
DICTI_CONTRACT(name, map); \
} \
\
static inline void \
M_C(name, _init_set)(dict_t map, const dict_t org) \
{ \
DICTI_CONTRACT(name, org); \
assert (map != org); \
map->used = org->used; \
map->lower_limit = org->lower_limit; \
map->upper_limit = org->upper_limit; \
M_C(name, _array_list_pair_init_set)(map->table, org->table); \
DICTI_CONTRACT(name, map); \
} \
\
static inline void \
M_C(name, _set)(dict_t map, const dict_t org) \
{ \
DICTI_CONTRACT(name, map); \
DICTI_CONTRACT(name, org); \
map->used = org->used; \
map->lower_limit = org->lower_limit; \
map->upper_limit = org->upper_limit; \
M_C(name, _array_list_pair_set)(map->table, org->table); \
DICTI_CONTRACT(name, map); \
} \
\
static inline void \
M_C(name,_clear)(dict_t map) \
{ \
DICTI_CONTRACT(name, map); \
M_C(name, _array_list_pair_clear)(map->table); \
} \
\
static inline void \
M_C(name, _init_move)(dict_t map, dict_t org) \
{ \
DICTI_CONTRACT(name, org); \
map->used = org->used; \
map->lower_limit = org->lower_limit; \
map->upper_limit = org->upper_limit; \
M_C(name, _array_list_pair_init_move)(map->table, org->table); \
DICTI_CONTRACT(name, map); \
} \
\
static inline void \
M_C(name, _swap)(dict_t d1, dict_t d2) \
{ \
DICTI_CONTRACT(name, d1); \
DICTI_CONTRACT(name, d2); \
M_SWAP (size_t, d1->used, d2->used); \
M_SWAP (size_t, d1->lower_limit, d2->lower_limit); \
M_SWAP (size_t, d1->upper_limit, d2->upper_limit); \
M_C(name, _array_list_pair_swap)(d1->table, d2->table); \
DICTI_CONTRACT(name, d1); \
DICTI_CONTRACT(name, d2); \
} \
\
static inline void \
M_C(name, _move)(dict_t map, dict_t org) \
{ \
DICTI_CONTRACT(name, map); \
DICTI_CONTRACT(name, org); \
assert (map != org); \
M_C(name,_clear)(map); \
M_C(name,_init_move)(map, org); \
DICTI_CONTRACT(name, map); \
} \
\
static inline void \
M_C(name,_clean)(dict_t map) \
{ \
M_C(name, _array_list_pair_clean)(map->table); \
M_C(name, _array_list_pair_resize)(map->table, DICTI_INITIAL_SIZE); \
map->lower_limit = DICTI_LOWER_BOUND(DICTI_INITIAL_SIZE); \
map->upper_limit = DICTI_UPPER_BOUND(DICTI_INITIAL_SIZE); \
map->used = 0; \
DICTI_CONTRACT(name, map); \
} \
\
static inline bool \
M_C(name,_empty_p)(const dict_t map) \
{ \
DICTI_CONTRACT(name, map); \
return map->used == 0; \
} \
\
static inline size_t \
M_C(name,_size)(const dict_t map) \
{ \
DICTI_CONTRACT(name, map); \
return map->used; \
} \
\
static inline value_type * \
M_C(name, _get)(const dict_t map, key_type const key) \
{ \
DICTI_CONTRACT(name, map); \
size_t hash = M_CALL_HASH(key_oplist, key); \
size_t i = hash & (M_C(name, _array_list_pair_size)(map->table) - 1); \
const M_C(name, _list_pair_ct) *list_ptr = \
M_C(name, _array_list_pair_cget)(map->table, i); \
M_C(name, _list_pair_it_ct) it; \
for(M_C(name, _list_pair_it)(it, *list_ptr); \
!M_C(name, _list_pair_end_p)(it); \
M_C(name, _list_pair_next)(it)) { \
pair_type *ref = M_C(name, _list_pair_ref)(it); \
M_IF(isStoreHash)(if ((*ref)->hash != hash) { continue; }, ) \
if (M_CALL_EQUAL(key_oplist, (*ref)->key, key)) \
return &(*ref)->M_IF(isSet)(key, value); \
} \
return NULL; \
} \
\
static inline value_type const * \
M_C(name, _cget)(const dict_t map, key_type const key) \
{ \
return M_CONST_CAST(value_type, M_C(name,_get)(map,key)); \
} \
\
static inline void \
M_C(name, _int_resize_up)(dict_t map) \
{ \
/* NOTE: Contract may not be fullfilled here */ \
size_t old_size = M_C(name, _array_list_pair_size)(map->table); \
size_t new_size = old_size * 2; \
if (M_UNLIKELY (new_size <= old_size)) { \
M_MEMORY_FULL((size_t)-1); \
} \
/* Resize the table of the dictionnary */ \
M_C(name, _array_list_pair_resize)(map->table, new_size); \
/* Move the items to the new upper part */ \
for(size_t i = 0; i < old_size; i++) { \
M_C(name, _list_pair_ct) *list = \
M_C(name, _array_list_pair_get)(map->table, i); \
if (M_C(name, _list_pair_empty_p)(*list)) \
continue; \
/* We need to scan each item and recompute its hash to know \
if it remains inplace or shall be moved to the upper part.*/ \
M_C(name, _list_pair_it_ct) it; \
M_C(name, _list_pair_it)(it, *list); \
while (!M_C(name, _list_pair_end_p)(it)) { \
M_C(name, _pair_ptr) pair = *M_C(name, _list_pair_ref)(it); \
size_t hash = M_IF(isStoreHash)(pair->hash, M_CALL_HASH(key_oplist, pair->key)); \
if ((hash & (new_size-1)) >= old_size) { \
assert( (hash & (new_size-1)) == (i + old_size)); \
M_C(name, _list_pair_ct) *new_list = \
M_C(name, _array_list_pair_get)(map->table, i + old_size); \
M_C(name, _list_pair_splice_back)(*new_list, *list, it); \
/* Splice_back has updated the iterator to the next one */ \
} else { \
M_C(name, _list_pair_next)(it); \
} \
} \
} \
map->upper_limit = DICTI_UPPER_BOUND(new_size); \
map->lower_limit = DICTI_LOWER_BOUND(new_size); \
} \
\
static inline void \
M_C(name, _int_resize_down)(dict_t map) \
{ \
/* NOTE: Contract may not be fullfilled here */ \
size_t old_size = M_C(name, _array_list_pair_size)(map->table); \
assert ((old_size % 2) == 0); \
size_t new_size = old_size / 2; \
assert (new_size >= DICTI_INITIAL_SIZE); \
/* Move all items from the upper part to the lower part of the table */ \
/* NOTE: We don't need to recompute the hash to move them! */ \
for(size_t i = new_size; i < old_size; i++) { \
M_C(name, _list_pair_ct) *list = \
M_C(name, _array_list_pair_get)(map->table, i); \
if (M_C(name, _list_pair_empty_p)(*list)) \
continue; \
M_C(name, _list_pair_ct) *new_list = \
M_C(name, _array_list_pair_get)(map->table, i - new_size); \
M_C(name, _list_pair_splice)(*new_list, *list); \
} \
/* Resize the table of the dictionary */ \
M_C(name, _array_list_pair_resize)(map->table, new_size); \
map->upper_limit = DICTI_UPPER_BOUND(new_size); \
map->lower_limit = DICTI_LOWER_BOUND(new_size); \
} \
\
static inline void \
M_IF(isSet)(M_C(name, _push), M_C(name, _set_at)) \
(dict_t map, key_type const key \
M_IF(isSet)(, M_DEFERRED_COMMA value_type const value)) \
{ \
DICTI_CONTRACT(name, map); \
\
size_t hash = M_CALL_HASH(key_oplist, key); \
size_t i = hash & (M_C(name, _array_list_pair_size)(map->table) - 1); \
M_C(name, _list_pair_ct) *list_ptr = \
M_C(name, _array_list_pair_get)(map->table, i); \
M_C(name, _list_pair_it_ct) it; \
for(M_C(name, _list_pair_it)(it, *list_ptr); \
!M_C(name, _list_pair_end_p)(it); \
M_C(name, _list_pair_next)(it)) { \
M_C(name, _pair_ptr) ref = *M_C(name, _list_pair_ref)(it); \
M_IF(isStoreHash)(if (ref->hash != hash) continue;, ) \
if (M_CALL_EQUAL(key_oplist,ref->key, key)) { \
M_CALL_SET(value_oplist, ref->value, value); \
return; \
} \
} \
M_C(name, _pair_init_set2)(*M_C(name, _list_pair_push_raw)(*list_ptr), \
M_IF(isStoreHash)(hash M_DEFERRED_COMMA,) \
key \
M_IF(isSet)(, M_DEFERRED_COMMA value)); \
map->used ++; \
if (M_UNLIKELY (map->used > map->upper_limit) ) \
M_C(name,_int_resize_up)(map); \
DICTI_CONTRACT(name, map); \
} \
\
static inline value_type * \
M_C(name, _get_at)(dict_t map, key_type const key) \
{ \
DICTI_CONTRACT(name, map); \
\
size_t hash = M_CALL_HASH(key_oplist, key); \
size_t i = hash & (M_C(name, _array_list_pair_size)(map->table) - 1); \
M_C(name, _list_pair_ct) *list_ptr = \
M_C(name, _array_list_pair_get)(map->table, i); \
M_C(name, _list_pair_it_ct) it; \
for(M_C(name, _list_pair_it)(it, *list_ptr); \
!M_C(name, _list_pair_end_p)(it); \
M_C(name, _list_pair_next)(it)) { \
M_C(name, _pair_ptr) ref = *M_C(name, _list_pair_ref)(it); \
M_IF(isStoreHash)(if (ref->hash != hash) continue;, ) \
if (M_CALL_EQUAL(key_oplist, ref->key, key)) { \
return &ref->M_IF(isSet)(key, value); \
} \
} \
pair_type *ref = M_C(name, _list_pair_push_new)(*list_ptr); \
M_IF(isStoreHash)(M_C(name, _pair_set_hash)(*ref, hash);,) \
M_C(name, _pair_set_key)(*ref, key); \
map->used ++; \
if (M_UNLIKELY (map->used > map->upper_limit) ) { \
M_C(name,_int_resize_up)(map); \
/* Even if the array is being resized, the pointer 'ref' \
shall still point to the same item in the bucket (it may still \
be in a different bucket) */ \
} \
DICTI_CONTRACT(name, map); \
return &(*ref)->M_IF(isSet)(key, value); \
} \
\
static inline bool \
M_C(name, _erase)(dict_t map, key_type const key) \
{ \
DICTI_CONTRACT(name, map); \
\
bool ret = false; \
size_t hash = M_CALL_HASH(key_oplist, key); \
size_t i = hash & (M_C(name, _array_list_pair_size)(map->table) - 1); \
M_C(name, _list_pair_ct) *list_ptr = \
M_C(name, _array_list_pair_get)(map->table, i); \
M_C(name, _list_pair_it_ct) it; \
for(M_C(name, _list_pair_it)(it, *list_ptr); \
!M_C(name, _list_pair_end_p)(it); \
M_C(name, _list_pair_next)(it)) { \
M_C(name, _pair_ptr) ref = *M_C(name, _list_pair_ref)(it); \
M_IF(isStoreHash)(if (ref->hash != hash) continue;, ) \
if (M_CALL_EQUAL(key_oplist, ref->key, key)) { \
M_C(name, _list_pair_remove)(*list_ptr, it); \
map->used --; \
ret = true; \
break; \
} \
} \
if (M_UNLIKELY (map->used < map->lower_limit) ) \
M_C(name, _int_resize_down)(map); \
return ret; \
} \
\
static inline void \
M_C(name, _it)(dict_it_t it, const dict_t d) \
{ \
DICTI_CONTRACT(name, d); \
M_C(name, _array_list_pair_it)(it->array_it, d->table); \
M_C(name, _list_pair_ct) *ref = \
M_C(name, _array_list_pair_ref)(it->array_it); \
M_C(name, _list_pair_it)(it->list_it, *ref); \
while (M_C(name, _list_pair_end_p)(it->list_it)) { \
M_C(name, _array_list_pair_next)(it->array_it); \
if (M_UNLIKELY (M_C(name, _array_list_pair_end_p)(it->array_it))) \
break; \
ref = M_C(name, _array_list_pair_ref)(it->array_it); \
M_C(name, _list_pair_it)(it->list_it, *ref); \
} \
} \
\
static inline void \
M_C(name, _it_set)(dict_it_t it, const dict_it_t ref) \
{ \
assert (it != NULL && ref != NULL); \
M_C(name, _array_list_pair_it_set)(it->array_it, \
ref->array_it); \
M_C(name, _list_pair_it_set)(it->list_it, ref->list_it); \
} \
\
static inline void \
M_C(name, _it_end)(dict_it_t it, const dict_t d) \
{ \
DICTI_CONTRACT(name, d); \
M_C(name, _array_list_pair_it_end)(it->array_it, d->table); \
} \
\
static inline bool \
M_C(name, _end_p)(const dict_it_t it) \
{ \
assert (it != NULL); \
return M_C(name, _list_pair_end_p)(it->list_it); \
} \
\
static inline void \
M_C(name, _next)(dict_it_t it) \
{ \
assert(it != NULL); \
M_C(name, _list_pair_next)(it->list_it); \
M_C(name, _list_pair_ct) *ref; \
while (M_C(name, _list_pair_end_p)(it->list_it)) { \
M_C(name, _array_list_pair_next)(it->array_it); \
if (M_C(name, _array_list_pair_end_p)(it->array_it)) \
break; \
ref = M_C(name, _array_list_pair_ref)(it->array_it); \
M_C(name, _list_pair_it)(it->list_it, *ref); \
} \
} \
\
static inline bool \
M_C(name, _last_p)(const dict_it_t it) \
{ \
assert (it != NULL); \
dict_it_t it2; \
M_C(name,_it_set)(it2, it); \
M_C(name, _next)(it2); \
return M_C(name, _end_p)(it2); \
} \
\
static inline bool \
M_C(name, _it_equal_p)(const dict_it_t it1, const dict_it_t it2) \
{ \
assert (it1 != NULL && it2 != NULL); \
return M_C(name, _list_pair_it_equal_p)(it1->list_it, \
it2->list_it); \
} \
\
static inline it_deref_t * \
M_C(name, _ref)(const dict_it_t it) \
{ \
assert(it != NULL); \
/* NOTE: partially unsafe if the user modify the 'key' \
in a non equivalent way */ \
M_IF(isSet)( \
return &(*M_C(name, _list_pair_ref)(it->list_it))->key; \
, \
return *M_C(name, _list_pair_ref)(it->list_it); \
) \
} \
\
static inline const it_deref_t * \
M_C(name, _cref)(const dict_it_t it) \
{ \
assert(it != NULL); \
M_IF(isSet)( \
return &(*M_C(name, _list_pair_cref)(it->list_it))->key; \
, \
return *M_C(name, _list_pair_cref)(it->list_it); \
) \
} \
\
M_IF_METHOD(EQUAL, value_oplist)( \
static inline bool \
M_C(name, _equal_p)(const dict_t dict1, const dict_t dict2) \
{ \
assert (dict1 != NULL && dict2 != NULL); \
/* NOTE: Key type has mandatory equal operator */ \
/* First the easy cases */ \
if (M_LIKELY (dict1->used != dict2->used)) \
return false; \
if (M_UNLIKELY (dict1->used == 0)) \
return true; \
/* Otherwise this is the slow path : \
both dictionary may not have arrays with the same size, but \
still the dictionnaries shall be equal as they contain the same \
items. */ \
dict_it_t it; \
for(M_C(name, _it)(it, dict1) ; \
!M_C(name, _end_p)(it); \
M_C(name, _next)(it)) { \
const it_deref_t *item = M_C(name, _cref)(it); \
value_type *ptr = M_C(name, _get)(dict2, M_IF(isSet)(*item, item->key)); \
if (ptr == NULL) \
return false; \
if (M_CALL_EQUAL(value_oplist, item->value, *ptr) == false) \
return false; \
} \
return true; \
} \
, /* no value equal */ ) \
\
DICTI_FUNC_ADDITIONAL_DEF2(name, key_type, key_oplist, value_type, value_oplist, isSet, dict_t, dict_it_t, it_deref_t)
/* Define additional functions for dictionnary (Common for all kinds of dictionnary).
Do not used any fields of the dictionnary but the public API */
#define DICTI_FUNC_ADDITIONAL_DEF2(name, key_type, key_oplist, value_type, value_oplist, isSet, dict_t, dict_it_t, it_deref_t) \
\
M_IF_METHOD_BOTH(GET_STR, key_oplist, value_oplist)( \
static inline void \
M_C(name, _get_str)(string_t str, const dict_t dict, const bool append) \
{ \
STRINGI_CONTRACT (str); \
(append ? string_cat_str : string_set_str) (str, "{"); \
dict_it_t it; \
bool print_comma = false; \
for (M_C(name, _it)(it, dict) ; \
!M_C(name, _end_p)(it); \
M_C(name, _next)(it)){ \
if (print_comma) \
string_push_back (str, ','); \
const it_deref_t *item = M_C(name, _cref)(it); \
M_IF(isSet)( \
M_CALL_GET_STR(key_oplist, str, *item, true); \
, \
M_CALL_GET_STR(key_oplist, str, item->key, true); \
string_push_back (str, ':'); \
M_CALL_GET_STR(value_oplist, str, item->value, true); \
) \
print_comma = true; \
} \
string_push_back (str, '}'); \
STRINGI_CONTRACT(str); \
} \
, /* no GET_STR */ ) \
\
M_IF_METHOD_BOTH(OUT_STR, key_oplist, value_oplist)( \
static inline void \
M_C(name, _out_str)(FILE *file, const dict_t dict) \
{ \
assert (file != NULL); \
fputc ('{', file); \
dict_it_t it; \
bool print_comma = false; \
for (M_C(name, _it)(it, dict) ; \
!M_C(name, _end_p)(it); \
M_C(name, _next)(it)){ \
if (print_comma) \
fputc (',', file); \
const it_deref_t *item = M_C(name, _cref)(it); \
M_IF(isSet)( \
M_CALL_OUT_STR(key_oplist, file, *item); \
, \
M_CALL_OUT_STR(key_oplist, file, item->key); \
fputc (':', file); \
M_CALL_OUT_STR(value_oplist, file, item->value); \
) \
print_comma = true; \
} \
fputc ('}', file); \
} \
, /* no OUT_STR */ ) \
\
M_IF_METHOD_BOTH(PARSE_STR, key_oplist, value_oplist)( \
static inline bool \
M_C(name, _parse_str)(dict_t dict, const char str[], const char **endp) \
{ \
assert (str != NULL); \
M_C(name, _clean)(dict); \
bool success = false; \
int c = *str++; \
if (M_UNLIKELY (c != '{')) goto exit; \
c = *str++; \
if (M_UNLIKELY (c == '}')) { success = true; goto exit;} \
if (M_UNLIKELY (c == 0)) goto exit; \
str--; \
key_type key; \
M_IF(isSet)( ,value_type value); \
M_CALL_INIT(key_oplist, key); \
M_CALL_INIT(value_oplist, value); \
do { \
while (isspace((int) *str)) str++; \
bool b = M_CALL_PARSE_STR(key_oplist, key, str, &str); \
M_IF(isSet)( \
if (b == false) { goto exit_clear; } \
M_C(name, _push)(dict, key); \
, \
do { c = *str++; } while (isspace(c)); \
if (b == false || c != ':') { goto exit_clear; } \
b = M_CALL_PARSE_STR(value_oplist, value, str, &str); \
if (b == false) { goto exit_clear; } \
M_C(name, _set_at)(dict, key, value); \
) \
do { c = *str++; } while (isspace(c)); \
} while (c == ','); \
success = (c == '}'); \
exit_clear: \
M_CALL_CLEAR(key_oplist, key); \
M_CALL_CLEAR(value_oplist, value); \
exit: \
if (endp) *endp = str; \
return success; \
} \
, /* no PARSE_STR */ ) \
\
M_IF_METHOD_BOTH(IN_STR, key_oplist, value_oplist)( \
static inline bool \
M_C(name, _in_str)(dict_t dict, FILE *file) \
{ \
assert (file != NULL); \
M_C(name, _clean)(dict); \
int c = fgetc(file); \
if (M_UNLIKELY (c != '{')) return false; \
c = fgetc(file); \
if (M_UNLIKELY (c == '}')) return true; \
if (M_UNLIKELY (c == EOF)) return true; \
ungetc(c, file); \
key_type key; \
M_CALL_INIT(key_oplist, key); \
M_IF(isSet)(, \
value_type value; \
M_CALL_INIT(value_oplist, value); \
) \
do { \
do { c = fgetc(file); } while (isspace(c)); \
ungetc(c, file); \
bool b = M_CALL_IN_STR(key_oplist, key, file); \
M_IF(isSet)( \
if (b == false) { break; } \
M_C(name, _push)(dict, key); \
, \
do { c = fgetc(file); } while (isspace(c)); \
if (b == false || c == EOF) { break; } \
if (c != ':') { c = 0; break; } \
b = M_CALL_IN_STR(value_oplist, value, file); \
if (b == false) { c = 0; break; } \
M_C(name, _set_at)(dict, key, value); \
) \
do { c = fgetc(file); } while (isspace(c)); \
} while (c == ','); \
M_CALL_CLEAR(key_oplist, key); \
M_IF(isSet)(, \
M_CALL_CLEAR(value_oplist, value); \
) \
return c == '}'; \
} \
, /* no IN_STR */ ) \
\
M_IF_METHOD_BOTH(OUT_SERIAL, key_oplist, value_oplist)( \
static inline m_serial_return_code_t \
M_C(name, _out_serial)(m_serial_write_t f, dict_t const t1) \
{ \
assert (f != NULL && f->m_interface != NULL); \
m_serial_local_t local; \
m_serial_return_code_t ret; \
const it_deref_t *item; \
bool first_done = false; \
dict_it_t it; \
/* Format is different between associative container \
& set container */ \
M_IF(isSet)( \
ret = f->m_interface->write_array_start(local, f, M_C(name, _size)(t1)); \
for (M_C(name, _it)(it, t1) ; \
!M_C(name, _end_p)(it); \
M_C(name, _next)(it)){ \
item = M_C(name, _cref)(it); \
if (first_done) \
ret |= f->m_interface->write_array_next(local, f); \
ret |= M_CALL_OUT_SERIAL(key_oplist, f, *item); \
first_done = true; \
} \
ret |= f->m_interface->write_array_end(local, f); \
, \
ret = f->m_interface->write_map_start(local, f, M_C(name, _size)(t1)); \
for (M_C(name, _it)(it, t1) ; \
!M_C(name, _end_p)(it); \
M_C(name, _next)(it)){ \
item = M_C(name, _cref)(it); \
if (first_done) \
ret |= f->m_interface->write_map_next(local, f); \
ret |= M_CALL_OUT_SERIAL(key_oplist, f, item->key); \
ret |= f->m_interface->write_map_value(local, f); \
ret |= M_CALL_OUT_SERIAL(value_oplist, f, item->value); \
first_done = true; \
} \
ret |= f->m_interface->write_map_end(local, f); \
) \
return ret & M_SERIAL_FAIL; \
} \
, /* no OUT_SERIAL */ ) \
\
M_IF_METHOD_BOTH(IN_SERIAL, key_oplist, value_oplist)( \
static inline m_serial_return_code_t \
M_C(name, _in_serial)(dict_t t1, m_serial_read_t f) \
{ \
assert (f != NULL && f->m_interface != NULL); \
m_serial_local_t local; \
m_serial_return_code_t ret; \
size_t estimated_size = 0; \
key_type key; \
M_C(name,_clean)(t1); \
M_IF(isSet)( \
ret = f->m_interface->read_array_start(local, f, &estimated_size); \
if (M_UNLIKELY (ret != M_SERIAL_OK_CONTINUE)) return ret; \
M_CALL_INIT(key_oplist, key); \
do { \
ret = M_CALL_IN_SERIAL(key_oplist, key, f); \
if (ret != M_SERIAL_OK_DONE) { break; } \
M_C(name, _push)(t1, key); \
} while ((ret = f->m_interface->read_array_next(local, f)) == M_SERIAL_OK_CONTINUE); \
M_CALL_CLEAR(key_oplist, key); \
, \
value_type value; \
ret = f->m_interface->read_map_start(local, f, &estimated_size); \
if (M_UNLIKELY (ret != M_SERIAL_OK_CONTINUE)) return ret; \
M_CALL_INIT(key_oplist, key); \
M_CALL_INIT (value_oplist, value); \
do { \
ret = M_CALL_IN_SERIAL(key_oplist, key, f); \
if (ret != M_SERIAL_OK_DONE) return M_SERIAL_FAIL; \
ret = f->m_interface->read_map_value(local, f); \
if (ret != M_SERIAL_OK_CONTINUE) return M_SERIAL_FAIL; \
ret = M_CALL_IN_SERIAL(value_oplist, value, f); \
if (ret != M_SERIAL_OK_DONE) return M_SERIAL_FAIL; \
M_C(name, _set_at)(t1, key, value); \
} while ((ret = f->m_interface->read_map_next(local, f)) == M_SERIAL_OK_CONTINUE); \
M_CALL_CLEAR(key_oplist, key); \
M_CALL_CLEAR(value_oplist, value); \
) /* End of IF isSet */ \
return ret; \
} \
, /* no in_serial */ ) \
\
M_IF(isSet)( \
static inline void \
M_C(name, _splice)(dict_t d1, dict_t d2) \
{ \
dict_it_t it; \
/* NOTE: Despite using set_at, the accessing of the item in d1 \
is not as random as other uses of the HASH table as d2 \
uses the same order than d1 */ \
for (M_C(name, _it)(it, d2); !M_C(name, _end_p)(it); M_C(name, _next)(it)){ \
const it_deref_t *item = M_C(name, _cref)(it); \
M_C(name, _push)(d1, *item); \
} \
M_C(name, _clean)(d2); \
} \
, \
M_IF_METHOD(UPDATE, value_oplist)( \
static inline void \
M_C(name, _splice)(dict_t d1, dict_t d2) \
{ \
dict_it_t it; \
/* NOTE: Despite using set_at, the accessing of the item in d1 \
is not as random as other uses of the HASH table as d2 \
uses the same order than d1 */ \
for (M_C(name, _it)(it, d2); !M_C(name, _end_p)(it); M_C(name, _next)(it)){ \
const struct M_C(name, _pair_s) *item = M_C(name, _cref)(it); \
value_type *ptr = M_C(name, _get)(d1, item->key); \
if (ptr == NULL) { \
M_C(name, _set_at)(d1, item->key, item->value); \
} else { \
M_CALL_UPDATE(value_oplist, *ptr, item->value); \
} \
} \
M_C(name, _clean)(d2); \
} \
, /* NO UPDATE */) ) \
\
/* HASH method for dictionnary itself seems hard to implement: \
we have to handle the case where two dictionnaries are structuraly \
different, but functionnaly identical (seems they have the same \
members, but put in a different order). \
We cannot iterator over the dictionary to compute a hash, as the \
order of the items in the dictionnary is not specified: they more \
or less follow the hash of the keys, but if the low bits of the \
hash of the key is equal, they order may be different. \
Or if the table of the dictionnary has different values (this may \
be avoided). */
/* Deferred evaluation for the definition,
so that all arguments are evaluated before further expansion */
#define DICTI_OPLIST_P1(arg) DICTI_OPLIST_P2 arg
/* Validation of the given oplists */
#define DICTI_OPLIST_P2(name, key_oplist, value_oplist) \
M_IF_OPLIST(key_oplist)(DICTI_OPLIST_P3, DICTI_OPLIST_FAILURE)(name, key_oplist, value_oplist)
#define DICTI_OPLIST_P3(name, key_oplist, value_oplist) \
M_IF_OPLIST(value_oplist)(DICTI_OPLIST_P4, DICTI_OPLIST_FAILURE)(name, key_oplist, value_oplist)
/* Prepare a clean compilation failure */
#define DICTI_OPLIST_FAILURE(name, key_oplist, value_oplist) \
((M_LIB_ERROR(ARGUMENT_OF_DICT_OPLIST_IS_NOT_AN_OPLIST, name, key_oplist, value_oplist)))
/* Define the oplist of a dictionnary
NOTE: IT_REF is not exported so that the contained appears as not modifiable
by algorithm.*/
#define DICTI_OPLIST_P4(name, key_oplist, value_oplist) \
(INIT(M_C(name, _init)), \
INIT_SET(M_C(name, _init_set)), \
INIT_WITH(API_1(M_INIT_KEY_VAI)), \
SET(M_C(name, _set)), \
CLEAR(M_C(name, _clear)), \
INIT_MOVE(M_C(name, _init_move)), \
MOVE(M_C(name, _move)), \
SWAP(M_C(name, _swap)), \
CLEAN(M_C(name, _clean)), \
TYPE(M_C(name, _ct)), \
SUBTYPE(M_C(name, _subtype_ct)), \
TEST_EMPTY(M_C(name,_empty_p)), \
IT_TYPE(M_C(name, _it_ct)), \
IT_FIRST(M_C(name,_it)), \
IT_SET(M_C(name, _it_set)), \
IT_END(M_C(name,_it_end)), \
IT_END_P(M_C(name,_end_p)), \
IT_LAST_P(M_C(name,_last_p)), \
IT_NEXT(M_C(name,_next)), \
IT_CREF(M_C(name,_cref)) \
,KEY_TYPE(M_C(name, _key_ct)) \
,VALUE_TYPE(M_C(name, _value_ct)) \
,SET_KEY(M_C(name, _set_at)) \
,GET_KEY(M_C(name, _get)) \
,GET_SET_KEY(M_C(name, _get_at)) \
,ERASE_KEY(M_C(name, _erase)) \
,KEY_OPLIST(key_oplist) \
,VALUE_OPLIST(value_oplist) \
,GET_SIZE(M_C(name, _size)) \
,M_IF_METHOD_BOTH(GET_STR, key_oplist, value_oplist)(GET_STR(M_C(name, _get_str)),) \
,M_IF_METHOD_BOTH(PARSE_STR, key_oplist, value_oplist)(PARSE_STR(M_C(name, _parse_str)),) \
,M_IF_METHOD_BOTH(OUT_STR, key_oplist, value_oplist)(OUT_STR(M_C(name, _out_str)),) \
,M_IF_METHOD_BOTH(IN_STR, key_oplist, value_oplist)(IN_STR(M_C(name, _in_str)),) \
,M_IF_METHOD_BOTH(OUT_SERIAL, key_oplist, value_oplist)(OUT_SERIAL(M_C(name, _out_serial)),) \
,M_IF_METHOD_BOTH(IN_SERIAL, key_oplist, value_oplist)(IN_SERIAL(M_C(name, _in_serial)),) \
,M_IF_METHOD(EQUAL, value_oplist)(EQUAL(M_C(name, _equal_p)),) \
,M_IF_METHOD(NEW, oplist)(NEW(M_GET_NEW key_oplist),) \
,M_IF_METHOD(REALLOC, oplist)(REALLOC(M_GET_REALLOC key_oplist),) \
,M_IF_METHOD(DEL, oplist)(DEL(M_GET_DEL key_oplist),) \
)
/* Deferred evaluation for the definition,
so that all arguments are evaluated before further expansion */
#define DICTI_SET_OPLIST_P1(arg) DICTI_SET_OPLIST_P2 arg
/* Validation of the given oplist */
#define DICTI_SET_OPLIST_P2(name, oplist) \
M_IF_OPLIST(oplist)(DICTI_SET_OPLIST_P3, DICTI_SET_OPLIST_FAILURE)(name, oplist)
/* Prepare a clean compilation failure */
#define DICTI_SET_OPLIST_FAILURE(name, oplist) \
((M_LIB_ERROR(ARGUMENT_OF_DICT_SET_OPLIST_IS_NOT_AN_OPLIST, name, oplist)))
/* Define the oplist of a set */
#define DICTI_SET_OPLIST_P3(name, oplist) \
(INIT(M_C(name, _init)), \
INIT_SET(M_C(name, _init_set)), \
INIT_WITH(API_1(M_INIT_VAI)), \
SET(M_C(name, _set)), \
CLEAR(M_C(name, _clear)), \
INIT_MOVE(M_C(name, _init_move)), \
MOVE(M_C(name, _move)), \
SWAP(M_C(name, _swap)), \
CLEAN(M_C(name, _clean)), \
TYPE(M_C(name, _ct)), \
SUBTYPE(M_C(name, _subtype_ct)), \
TEST_EMPTY(M_C(name,_empty_p)), \
PUSH(M_C(name,_push)), \
KEY_TYPE(M_C(name, _key_ct)), \
VALUE_TYPE(M_C(name, _key_ct)), \
GET_KEY(M_C(name, _get)), \
GET_SET_KEY(M_C(name, _get_at)), \
ERASE_KEY(M_C(name, _erase)), \
KEY_OPLIST(oplist), \
VALUE_OPLIST(oplist), \
GET_SIZE(M_C(name, _size)), \
IT_TYPE(M_C(name, _it_ct)), \
IT_FIRST(M_C(name,_it)), \
IT_SET(M_C(name, _it_set)), \
IT_END(M_C(name,_it_end)), \
IT_END_P(M_C(name,_end_p)), \
IT_LAST_P(M_C(name,_last_p)), \
IT_NEXT(M_C(name,_next)), \
IT_REF(M_C(name,_ref)), \
IT_CREF(M_C(name,_cref)) \
,OPLIST(oplist) \
,M_IF_METHOD(GET_STR, oplist)(GET_STR(M_C(name, _get_str)),) \
,M_IF_METHOD(PARSE_STR, oplist)(PARSE_STR(M_C(name, _parse_str)),) \
,M_IF_METHOD(OUT_STR, oplist)(OUT_STR(M_C(name, _out_str)),) \
,M_IF_METHOD(IN_STR, oplist)(IN_STR(M_C(name, _in_str)),) \
,M_IF_METHOD(OUT_SERIAL, oplist)(OUT_SERIAL(M_C(name, _out_serial)),) \
,M_IF_METHOD(IN_SERIAL, oplist)(IN_SERIAL(M_C(name, _in_serial)),) \
,EQUAL(M_C(name, _equal_p)), \
,M_IF_METHOD(NEW, oplist)(NEW(M_GET_NEW oplist),) \
,M_IF_METHOD(REALLOC, oplist)(REALLOC(M_GET_REALLOC oplist),) \
,M_IF_METHOD(DEL, oplist)(DEL(M_GET_DEL oplist),) \
)
/* Define Lower Bound for hash table (TODO: Common macro for both implementation) */
#ifndef DICTI_LOWER_BOUND
#define DICTI_LOWER_BOUND(s) ((s) <= DICTI_INITIAL_SIZE ? 0 : (s) * 1 / 4)
#endif
/* Define Lower Bound for hash table (TODO: Common macro for both implementation) */
#ifndef DICTI_UPPER_BOUND
#define DICTI_UPPER_BOUND(s) ((s) * 2 / 3)
#endif
/* Define initial size of the hash table */
#ifndef DICTI_INITIAL_SIZE
#define DICTI_INITIAL_SIZE 16
#endif
#define DICTI_CONTRACT(name, map) do { \
assert(map != NULL); \
assert(map->used <= map->upper_limit); \
assert(map->upper_limit >= DICTI_UPPER_BOUND(DICTI_INITIAL_SIZE)); \
assert(map->used >= map->lower_limit); \
assert(M_POWEROF2_P(M_C(name, _array_list_pair_size)(map->table))); \
} while (0)
/****************************************************************************************/
/* Open Addressing implementation */
/****************************************************************************************/
enum dicti_oa_element_e {
DICTI_OA_EMPTY = 0, DICTI_OA_DELETED = 1
};
/* Performing Quadratic probing
Replace it by '1' to perform linear probing */
#ifndef DICTI_OA_PROBING
#define DICTI_OA_PROBING(s) ((s)++)
#endif
/* Lower Bound of the hash table (TODO: Common macro for both dictionnary) */
#ifndef DICTI_OA_LOWER_BOUND
#define DICTI_OA_LOWER_BOUND 0.2
#endif
/* Upper Bound of the hash table (TODO: Common macro for both dictionnary) */
#ifndef DICTI_OA_UPPER_BOUND
#define DICTI_OA_UPPER_BOUND 0.7
#endif
#define DICTI_OA_CONTRACT(dict) do { \
assert ( (dict) != NULL); \
assert( (dict)->lower_limit <= (dict)->count); \
assert( (dict)->count <= (dict)->upper_limit ); \
assert( (dict)->data != NULL); \
assert( M_POWEROF2_P((dict)->mask+1)); \
assert( (dict)->mask+1 >= DICTI_INITIAL_SIZE); \
assert( (dict)->count <= (dict)->mask+1); \
} while (0)
#define DICTI_OA_DEF_P1(args) DICTI_OA_DEF_P2 args
/* Validate the key oplist before going further */
#define DICTI_OA_DEF_P2(name, key_type, key_oplist, value_type, value_oplist, dict_t, dict_it_t, it_deref_t) \
M_IF_OPLIST(key_oplist)(DICTI_OA_DEF_P3, DICTI_OA_DEF_FAILURE)(name, key_type, key_oplist, value_type, value_oplist, dict_t, dict_it_t, it_deref_t)
/* Validate the value oplist before going further */
#define DICTI_OA_DEF_P3(name, key_type, key_oplist, value_type, value_oplist, dict_t, dict_it_t, it_deref_t) \
M_IF_OPLIST(value_oplist)(DICTI_OA_DEF_P4, DICTI_OA_DEF_FAILURE)(name, key_type, key_oplist, value_type, value_oplist, dict_t, dict_it_t, it_deref_t)
/* Stop processing with a compilation failure */
#define DICTI_OA_DEF_FAILURE(name, key_type, key_oplist, value_type, value_oplist, dict_t, dict_it_t, it_deref_t) \
M_STATIC_FAILURE(M_LIB_NOT_AN_OPLIST, "(DICT_OA_DEF2): at least one of the given argument is not a valid oplist: " M_AS_STR(key_oplist) " / " M_AS_STR(value_oplist) )
#define DICTI_OA_DEF_P4(name, key_type, key_oplist, value_type, value_oplist, dict_t, dict_it_t, it_deref_t) \
DICTI_OA_DEF_P5(name, key_type, key_oplist, value_type, value_oplist, 0, \
DICTI_OA_LOWER_BOUND, DICTI_OA_UPPER_BOUND, \
dict_t, dict_it_t, it_deref_t)
#define DICTI_OASET_DEF_P1(args) DICTI_OASET_DEF_P2 args
/* Validate the value oplist before going further */
#define DICTI_OASET_DEF_P2(name, key_type, key_oplist, dict_t, dict_it_t, it_deref_t) \
M_IF_OPLIST(key_oplist)(DICTI_OASET_DEF_P4, DICTI_OASET_DEF_FAILURE)(name, key_type, key_oplist, dict_t, dict_it_t, it_deref_t)
/* Stop processing with a compilation failure */
#define DICTI_OASET_DEF_FAILURE(name, key_type, key_oplist, dict_t, dict_it_t, it_deref_t) \
M_STATIC_FAILURE(M_LIB_NOT_AN_OPLIST, "(DICT_OASET_DEF): the given argument is not a valid oplist: " M_AS_STR(key_oplist) )
#define DICTI_OASET_DEF_P4(name, key_type, key_oplist, dict_t, dict_it_t, it_deref_t) \
DICTI_OA_DEF_P5(name, key_type, key_oplist, key_type, M_EMPTY_OPLIST, 1, \
DICTI_OA_LOWER_BOUND, DICTI_OA_UPPER_BOUND, dict_t, dict_it_t, it_deref_t )
#define DICTI_OA_DEF_P5(name, key_type, key_oplist, value_type, value_oplist, isSet, coeff_down, coeff_up, dict_t, dict_it_t, it_deref_t) \
\
/* NOTE: \
if isSet is true, all methods of value_oplist are NOP methods */ \
\
typedef struct M_C(name, _pair_s) { \
key_type key; \
M_IF(isSet)( , value_type value;) \
} M_C(name, _pair_ct); \
\
/* Define type returned by the _ref method of an iterator */ \
M_IF(isSet)( \
typedef key_type it_deref_t; \
, \
typedef struct M_C(name, _pair_s) it_deref_t; \
) \
\
M_CHECK_COMPATIBLE_OPLIST(name, 1, key_type, key_oplist) \
M_CHECK_COMPATIBLE_OPLIST(name, 2, value_type, value_oplist) \
\
/* NOTE: We don't want a real oplist for this sub type */ \
ARRAY_DEF(M_C(name, _array_pair), M_C(name, _pair_ct), \
(INIT(M_NOTHING_DEFAULT), SET(M_MEMCPY_DEFAULT), \
INIT_SET(M_MEMCPY_DEFAULT), CLEAR(M_NOTHING_DEFAULT))) \
\
typedef struct M_C(name,_s) { \
size_t mask, count, count_delete; \
size_t upper_limit, lower_limit; \
struct M_C(name, _pair_s) *data; \
} dict_t[1]; \
typedef struct M_C(name, _s) *M_C(name, _ptr); \
typedef const struct M_C(name, _s) *M_C(name, _srcptr); \
\
typedef struct M_C(name, _it_s) { \
const struct M_C(name,_s) *dict; \
size_t index; \
} dict_it_t[1]; \
\
/* Define internal types for oplist */ \
typedef dict_t M_C(name, _ct); \
typedef it_deref_t M_C(name, _subtype_ct); \
typedef key_type M_C(name, _key_ct); \
typedef value_type M_C(name, _value_ct); \
typedef dict_it_t M_C(name, _it_ct); \
\
static inline void \
M_C(name,_int_limit)(dict_t dict, size_t size) \
{ \
/* FIXME: Overflow not handled. What to do in case of it? */ \
dict->upper_limit = (size_t) ((double) size * coeff_up) - 1; \
dict->lower_limit = (size <= DICTI_INITIAL_SIZE) ? 0 : (size_t) ((double) size * coeff_down) ; \
} \
\
static inline void \
M_C(name, _init)(dict_t dict) \
{ \
assert(0 <= (coeff_down) && (coeff_down)*2 < (coeff_up) && (coeff_up) < 1); \
dict->mask = DICTI_INITIAL_SIZE-1; \
dict->count = 0; \
dict->count_delete = 0; \
M_C(name,_int_limit)(dict, DICTI_INITIAL_SIZE); \
dict->data = M_CALL_REALLOC(key_oplist, M_C(name, _pair_ct), NULL, DICTI_INITIAL_SIZE); \
if (dict->data == NULL) { \
M_MEMORY_FULL(sizeof (M_C(name, _pair_ct)) * DICTI_INITIAL_SIZE); \
return ; \
} \
for(size_t i = 0; i < DICTI_INITIAL_SIZE; i++) { \
M_CALL_OOR_SET(key_oplist, dict->data[i].key, DICTI_OA_EMPTY); \
} \
DICTI_OA_CONTRACT(dict); \
} \
\
static inline void \
M_C(name, _clear)(dict_t dict) \
{ \
DICTI_OA_CONTRACT(dict); \
for(size_t i = 0; i <= dict->mask; i++) { \
if (!M_CALL_OOR_EQUAL(key_oplist, dict->data[i].key, DICTI_OA_EMPTY) \
&& !M_CALL_OOR_EQUAL(key_oplist, dict->data[i].key, DICTI_OA_DELETED)) { \
M_CALL_CLEAR(key_oplist, dict->data[i].key); \
M_CALL_CLEAR(value_oplist, dict->data[i].value); \
} \
} \
M_CALL_FREE(key_oplist, dict->data); \
/* Not really needed, but safer */ \
dict->mask = 0; \
dict->data = NULL; \
} \
\
static inline value_type * \
M_C(name, _get)(const dict_t dict, key_type const key) \
{ \
DICTI_OA_CONTRACT(dict); \
/* NOTE: Key can not be the representation of empty or deleted */ \
assert (!M_CALL_OOR_EQUAL(key_oplist, key, DICTI_OA_EMPTY)); \
assert (!M_CALL_OOR_EQUAL(key_oplist, key, DICTI_OA_DELETED)); \
\
M_C(name, _pair_ct) *const data = dict->data; \
const size_t mask = dict->mask; \
size_t p = M_CALL_HASH(key_oplist, key) & mask; \
\
/* Random access, and probably cache miss */ \
if (M_LIKELY (M_CALL_EQUAL(key_oplist, data[p].key, key)) ) \
return &data[p].M_IF(isSet)(key, value); \
else if (M_LIKELY (M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_EMPTY)) ) \
return NULL; \
\
/* Unlikely case */ \
size_t s = 1; \
do { \
p = (p + DICTI_OA_PROBING(s)) & mask; \
if (M_CALL_EQUAL(key_oplist, data[p].key, key)) \
return &data[p].M_IF(isSet)(key, value); \
assert (s <= dict->mask); \
} while (!M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_EMPTY) ); \
\
return NULL; \
} \
\
static inline value_type const * \
M_C(name, _cget)(const dict_t map, key_type const key) \
{ \
return M_CONST_CAST(value_type, M_C(name,_get)(map,key)); \
} \
\
M_IF_DEBUG( \
static inline bool \
M_C(name,_int_control_after_resize)(const dict_t h) \
{ \
/* This function checks if the reshashing of the dict is ok */ \
M_C(name, _pair_ct) *data = h->data; \
size_t empty = 0; \
size_t del = 0; \
/* Count the number of empty elements and the number of deleted */ \
for(size_t i = 0 ; i <= h->mask ; i++) { \
empty += M_CALL_OOR_EQUAL(key_oplist, data[i].key, DICTI_OA_EMPTY); \
del += M_CALL_OOR_EQUAL(key_oplist, data[i].key, DICTI_OA_DELETED); \
} \
assert(del == 0); \
assert(empty + h->count == h->mask + 1); \
return true; \
} \
) \
\
static inline void \
M_C(name,_int_resize_up)(dict_t h, size_t newSize, bool updateLimit) \
{ \
size_t oldSize = h->mask+1; \
assert (newSize >= oldSize); \
assert (M_POWEROF2_P(newSize)); \
M_C(name, _pair_ct) *data = h->data; \
/* resize can be called just to delete the items */ \
if (newSize > oldSize) { \
data = M_CALL_REALLOC(key_oplist, M_C(name, _pair_ct), data, newSize); \
if (M_UNLIKELY (data == NULL) ) { \
M_MEMORY_FULL(sizeof (M_C(name, _pair_ct)) * newSize); \
return ; \
} \
\
/* First mark the extended space as empty */ \
for(size_t i = oldSize ; i < newSize; i++) \
M_CALL_OOR_SET(key_oplist, data[i].key, DICTI_OA_EMPTY); \
} \
\
/* Then let's rehash all the entries in their **exact** position. \
If we can't, let's put them in the 'tmp' array. \
It has been measured that the size of this 'tmp' array is \
around 6% of the size of updated dictionnary. \
NOTE: This should be much cache friendly than typical hash code */ \
M_C(name, _array_pair_ct) tmp; \
M_C(name, _array_pair_init)(tmp); \
const size_t mask = (newSize -1); \
\
for(size_t i = 0 ; i < oldSize; i++) { \
if (!M_CALL_OOR_EQUAL(key_oplist, data[i].key, DICTI_OA_DELETED) && !M_CALL_OOR_EQUAL(key_oplist, data[i].key, DICTI_OA_EMPTY)) { \
size_t p = M_CALL_HASH(key_oplist, data[i].key) & mask; \
if (p != i) { \
if (M_LIKELY (M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_EMPTY) || M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_DELETED))) { \
M_DO_INIT_MOVE(key_oplist, data[p].key, data[i].key); \
M_DO_INIT_MOVE(value_oplist, data[p].value, data[i].value); \
} else { \
M_C(name, _pair_ct) *ptr = M_C(name, _array_pair_push_raw) (tmp); \
M_DO_INIT_MOVE(key_oplist, ptr->key, data[i].key); \
M_DO_INIT_MOVE(value_oplist, ptr->value, data[i].value); \
} \
M_CALL_OOR_SET(key_oplist, data[i].key, DICTI_OA_EMPTY); \
} \
} else { \
M_CALL_OOR_SET(key_oplist, data[i].key, DICTI_OA_EMPTY); \
} \
} \
\
/* Let's put back the entries in the tmp array in their right place */ \
/* NOTE: There should be very few entries in this array \
which contains what we weren't be able to fit in the first pass */ \
while (M_C(name, _array_pair_size)(tmp) > 0) { \
M_C(name, _pair_ct) const *item = M_C(name, _array_pair_back)(tmp); \
size_t p = M_CALL_HASH(key_oplist, item->key) & mask; \
/* NOTE: since the first pass, the bucket might be free now */ \
if (!M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_EMPTY)) { \
size_t s = 1; \
do { \
p = (p + DICTI_OA_PROBING(s)) & mask; \
assert (s <= h->mask); \
} while (!M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_EMPTY) ); \
} \
M_C(name, _array_pair_pop_move)(&data[p], tmp); \
} \
\
M_C(name, _array_pair_clear) (tmp); \
h->mask = newSize-1; \
h->count_delete = h->count; \
if (updateLimit == true) { \
M_C(name,_int_limit)(h, newSize); \
} \
h->data = data; \
M_IF_DEBUG (assert (M_C(name,_int_control_after_resize)(h));) \
DICTI_OA_CONTRACT(h); \
} \
\
static inline void \
M_IF(isSet)(M_C(name, _push), M_C(name,_set_at)) \
(dict_t dict, key_type const key \
M_IF(isSet)(, M_DEFERRED_COMMA value_type const value) ) \
{ \
DICTI_OA_CONTRACT(dict); \
/* NOTE: key can not be the representation of empty or deleted */ \
assert (!M_CALL_OOR_EQUAL(key_oplist, key, DICTI_OA_EMPTY)); \
assert (!M_CALL_OOR_EQUAL(key_oplist, key, DICTI_OA_DELETED)); \
\
M_C(name, _pair_ct) *const data = dict->data; \
const size_t mask = dict->mask; \
size_t p = M_CALL_HASH(key_oplist, key) & mask; \
\
/* NOTE: Likely cache miss */ \
if (M_UNLIKELY (M_CALL_EQUAL(key_oplist, data[p].key, key)) ) { \
M_CALL_SET(value_oplist, data[p].value, value); \
return; \
} \
if (M_UNLIKELY (!M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_EMPTY) ) ) { \
size_t delPos = SIZE_MAX; \
if (M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_DELETED)) delPos = p; \
size_t s = 1U; \
do { \
p = (p + DICTI_OA_PROBING(s)) & mask; \
if (M_CALL_EQUAL(key_oplist, data[p].key, key)) { \
M_CALL_SET(value_oplist, data[p].value, value); \
return; \
} \
assert (s <= dict->mask); \
if (M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_DELETED) && delPos == (size_t)-1) delPos = p; \
} while (!M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_EMPTY) ); \
if (delPos != SIZE_MAX) { \
p = delPos; \
dict->count_delete --; \
} \
} \
\
M_CALL_INIT_SET(key_oplist, data[p].key, key); \
M_CALL_INIT_SET(value_oplist, data[p].value, value); \
dict->count++; \
dict->count_delete ++; \
\
if (M_UNLIKELY (dict->count_delete >= dict->upper_limit)) { \
size_t newSize = dict->mask+1; \
if (dict->count > (dict->mask / 2)) { \
newSize += newSize; \
if (M_UNLIKELY (newSize <= dict->mask+1)) { \
M_MEMORY_FULL((size_t)-1); \
} \
} \
M_C(name,_int_resize_up)(dict, newSize, true); \
} \
DICTI_OA_CONTRACT(dict); \
} \
\
static inline value_type * \
M_C(name,_get_at)(dict_t dict, key_type const key) \
{ \
DICTI_OA_CONTRACT(dict); \
/* NOTE: key can not be the representation of empty or deleted */ \
assert (!M_CALL_OOR_EQUAL(key_oplist, key, DICTI_OA_EMPTY)); \
assert (!M_CALL_OOR_EQUAL(key_oplist, key, DICTI_OA_DELETED)); \
\
M_C(name, _pair_ct) *const data = dict->data; \
const size_t mask = dict->mask; \
size_t p = M_CALL_HASH(key_oplist, key) & mask; \
\
if (M_CALL_EQUAL(key_oplist, data[p].key, key)) { \
return &data[p].M_IF(isSet)(key, value); \
} \
if (M_UNLIKELY (!M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_EMPTY) ) ) { \
size_t delPos = SIZE_MAX; \
if (M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_DELETED)) delPos = p; \
size_t s = 1U; \
do { \
p = (p + DICTI_OA_PROBING(s)) & mask; \
if (M_CALL_EQUAL(key_oplist, data[p].key, key)) { \
return &data[p].M_IF(isSet)(key, value); \
} \
assert (s <= dict->mask); \
if (M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_DELETED) && delPos == (size_t)-1) delPos = p; \
} while (!M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_EMPTY) ); \
if (delPos != SIZE_MAX) { \
p = delPos; \
dict->count_delete --; \
} \
} \
\
M_CALL_INIT_SET(key_oplist, data[p].key, key); \
M_CALL_INIT(value_oplist, data[p].value); \
dict->count++; \
dict->count_delete ++; \
\
if (M_UNLIKELY (dict->count_delete >= dict->upper_limit)) { \
size_t newSize = dict->mask+1; \
if (dict->count > (dict->mask / 2)) { \
newSize += newSize; \
if (M_UNLIKELY (newSize <= dict->mask+1)) { \
M_MEMORY_FULL((size_t)-1); \
} \
} \
M_C(name,_int_resize_up)(dict, newSize, true); \
/* data is now invalid */ \
return M_C(name, _get)(dict, key); \
} \
DICTI_OA_CONTRACT(dict); \
return &data[p].M_IF(isSet)(key, value); \
} \
\
static inline void \
M_C(name,_int_resize_down)(dict_t h, size_t newSize) \
{ \
size_t oldSize = h->mask+1; \
assert (newSize <= oldSize && M_POWEROF2_P(newSize)); \
if (M_UNLIKELY (newSize < DICTI_INITIAL_SIZE)) \
newSize = DICTI_INITIAL_SIZE; \
const size_t mask = newSize -1; \
M_C(name, _pair_ct) *data = h->data; \
M_C(name, _array_pair_ct) tmp; \
M_C(name, _array_pair_init)(tmp); \
\
/* Pass 1: scan lower entries, and move them if needed */ \
for(size_t i = 0; i < newSize; i++) { \
if (M_CALL_OOR_EQUAL(key_oplist, data[i].key, DICTI_OA_EMPTY)) \
continue; \
if (M_CALL_OOR_EQUAL(key_oplist, data[i].key, DICTI_OA_DELETED)) { \
M_CALL_OOR_SET(key_oplist, data[i].key, DICTI_OA_EMPTY); \
continue; \
} \
size_t p = M_CALL_HASH(key_oplist, data[i].key) & mask; \
if (p != i) { \
if (M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_EMPTY) || M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_DELETED)) { \
M_DO_INIT_MOVE(key_oplist, data[p].key, data[i].key); \
M_DO_INIT_MOVE(value_oplist, data[p].value, data[i].value); \
} else { \
M_C(name, _pair_ct) *ptr = M_C(name, _array_pair_push_raw) (tmp); \
M_DO_INIT_MOVE(key_oplist, ptr->key, data[i].key); \
M_DO_INIT_MOVE(value_oplist, ptr->value, data[i].value); \
} \
M_CALL_OOR_SET(key_oplist, data[i].key, DICTI_OA_EMPTY); \
} \
} \
/* Pass 2: scan upper entries and move them back */ \
for(size_t i = newSize; i < oldSize; i++) { \
if (!M_CALL_OOR_EQUAL(key_oplist, data[i].key, DICTI_OA_DELETED) \
&& !M_CALL_OOR_EQUAL(key_oplist, data[i].key, DICTI_OA_EMPTY)) { \
size_t p = M_CALL_HASH(key_oplist, data[i].key) & mask; \
assert (p < i); \
if (!M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_EMPTY)) { \
size_t s = 1; \
do { \
p = (p + DICTI_OA_PROBING(s)) & mask; \
assert (s <= h->mask); \
} while (!M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_EMPTY) ); \
} \
M_DO_INIT_MOVE(key_oplist, data[p].key, data[i].key); \
M_DO_INIT_MOVE(value_oplist, data[p].value, data[i].value); \
} \
} \
/* Pass 3: scan moved entries and move them back */ \
while (M_C(name, _array_pair_size)(tmp) > 0) { \
M_C(name, _pair_ct) const *item = M_C(name, _array_pair_back)(tmp); \
size_t p = M_CALL_HASH(key_oplist, item->key) & mask; \
if (!M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_EMPTY)) { \
size_t s = 1; \
do { \
p = (p + DICTI_OA_PROBING(s)) & mask; \
assert (s <= h->mask); \
} while (!M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_EMPTY) ); \
} \
M_C(name, _array_pair_pop_move)(&data[p], tmp); \
} \
\
M_C(name, _array_pair_clear) (tmp); \
h->count_delete = h->count; \
if (newSize != oldSize) { \
h->mask = newSize-1; \
M_C(name,_int_limit)(h, newSize); \
h->data = M_CALL_REALLOC(key_oplist, M_C(name, _pair_ct), data, newSize); \
assert (h->data != NULL); \
} \
M_IF_DEBUG (assert (M_C(name,_int_control_after_resize)(h));) \
assert (h->lower_limit < h->count && h->count < h->upper_limit); \
DICTI_OA_CONTRACT(h); \
} \
\
static inline bool \
M_C(name,_erase)(dict_t dict, const key_type key) \
{ \
DICTI_OA_CONTRACT(dict); \
/* NOTE: key can't be the representation of empty or deleted */ \
assert (!M_CALL_OOR_EQUAL(key_oplist, key, DICTI_OA_EMPTY)); \
assert (!M_CALL_OOR_EQUAL(key_oplist, key, DICTI_OA_DELETED)); \
\
M_C(name, _pair_ct) *const data = dict->data; \
const size_t mask = dict->mask; \
size_t p = M_CALL_HASH(key_oplist, key) & mask; \
\
/* Random access, and probably cache miss */ \
if (M_UNLIKELY (!M_CALL_EQUAL(key_oplist, data[p].key, key)) ) { \
if (M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_EMPTY)) \
return false; \
size_t s = 1; \
do { \
p = (p + DICTI_OA_PROBING(s)) & mask; \
if (M_CALL_OOR_EQUAL(key_oplist, data[p].key, DICTI_OA_EMPTY) ) \
return false; \
assert (s <= dict->mask); \
} while (!M_CALL_EQUAL(key_oplist, data[p].key, key)); \
} \
M_CALL_CLEAR(key_oplist, data[p].key); \
M_CALL_CLEAR(value_oplist, data[p].value); \
M_CALL_OOR_SET(key_oplist, data[p].key, DICTI_OA_DELETED); \
assert (dict->count >= 1); \
dict->count--; \
if (M_UNLIKELY (dict->count < dict->lower_limit)) { \
M_C(name,_int_resize_down)(dict, (dict->mask+1) >> 1); \
} \
DICTI_OA_CONTRACT(dict); \
return true; \
} \
\
static inline bool \
M_C(name,_empty_p)(const dict_t dict) \
{ \
DICTI_OA_CONTRACT(dict); \
return dict->count == 0; \
} \
\
static inline size_t \
M_C(name,_size)(const dict_t dict) \
{ \
DICTI_OA_CONTRACT(dict); \
return dict->count; \
} \
\
static inline void \
M_C(name, _init_set)(dict_t map, const dict_t org) \
{ \
DICTI_OA_CONTRACT(org); \
assert (map != org); \
map->mask = org->mask; \
map->count = org->count; \
map->count_delete = org->count_delete; \
map->upper_limit = org->upper_limit; \
map->lower_limit = org->lower_limit; \
map->data = M_CALL_REALLOC(key_oplist, M_C(name, _pair_ct), NULL, map->mask+1); \
if (map->data == NULL) { \
M_MEMORY_FULL(sizeof (M_C(name, _pair_ct)) * (map->mask+1)); \
return ; \
} \
for(size_t i = 0; i <= org->mask; i++) { \
if (M_CALL_OOR_EQUAL(key_oplist, org->data[i].key, DICTI_OA_EMPTY)) { \
M_CALL_OOR_SET(key_oplist, map->data[i].key, DICTI_OA_EMPTY); \
} else if (M_CALL_OOR_EQUAL(key_oplist, org->data[i].key, DICTI_OA_DELETED)) { \
M_CALL_OOR_SET(key_oplist, map->data[i].key, DICTI_OA_DELETED); \
} else { \
M_CALL_INIT_SET(key_oplist, map->data[i].key, org->data[i].key); \
M_CALL_INIT_SET(value_oplist, map->data[i].value, org->data[i].value); \
} \
} \
DICTI_OA_CONTRACT(map); \
} \
\
static inline void \
M_C(name, _set)(dict_t map, const dict_t org) \
{ \
DICTI_OA_CONTRACT(map); \
DICTI_OA_CONTRACT(org); \
if (M_LIKELY (map != org)) { \
M_C(name, _clear)(map); \
M_C(name, _init_set)(map, org); \
} \
DICTI_OA_CONTRACT(map); \
} \
\
static inline void \
M_C(name, _init_move)(dict_t map, dict_t org) \
{ \
DICTI_OA_CONTRACT(org); \
assert (map != org); \
map->mask = org->mask; \
map->count = org->count; \
map->count_delete = org->count_delete; \
map->upper_limit = org->upper_limit; \
map->lower_limit = org->lower_limit; \
map->data = org->data; \
/* Mark org as cleared (safety) */ \
org->mask = 0; \
org->data = NULL; \
DICTI_OA_CONTRACT(map); \
} \
\
static inline void \
M_C(name, _move)(dict_t map, dict_t org) \
{ \
DICTI_OA_CONTRACT(map); \
DICTI_OA_CONTRACT(org); \
if (M_LIKELY (map != org)) { \
M_C(name, _clear)(map); \
M_C(name, _init_move)(map, org); \
} \
DICTI_OA_CONTRACT(map); \
} \
\
static inline void \
M_C(name, _swap)(dict_t d1, dict_t d2) \
{ \
DICTI_OA_CONTRACT(d1); \
DICTI_OA_CONTRACT(d2); \
M_SWAP (size_t, d1->mask, d2->mask); \
M_SWAP (size_t, d1->count, d2->count); \
M_SWAP (size_t, d1->count_delete, d2->count_delete); \
M_SWAP (size_t, d1->upper_limit, d2->upper_limit); \
M_SWAP (size_t, d1->lower_limit, d2->lower_limit); \
M_SWAP (M_C(name, _pair_ct) *, d1->data, d2->data); \
DICTI_OA_CONTRACT(d1); \
DICTI_OA_CONTRACT(d2); \
} \
\
static inline void \
M_C(name, _clean)(dict_t d) \
{ \
DICTI_OA_CONTRACT(d); \
for(size_t i = 0; i <= d->mask; i++) { \
if (!M_CALL_OOR_EQUAL(key_oplist, d->data[i].key, DICTI_OA_EMPTY) \
&& !M_CALL_OOR_EQUAL(key_oplist, d->data[i].key, DICTI_OA_DELETED)) { \
M_CALL_CLEAR(key_oplist, d->data[i].key); \
M_CALL_CLEAR(value_oplist, d->data[i].value); \
} \
} \
d->count = 0; \
d->count_delete = 0; \
d->mask = DICTI_INITIAL_SIZE-1; \
M_C(name,_int_limit)(d, DICTI_INITIAL_SIZE); \
d->data = M_CALL_REALLOC(key_oplist, M_C(name, _pair_ct), \
d->data, DICTI_INITIAL_SIZE); \
assert(d->data != NULL); \
for(size_t i = 0; i <= d->mask; i++) { \
M_CALL_OOR_SET(key_oplist, d->data[i].key, DICTI_OA_EMPTY); \
} \
DICTI_OA_CONTRACT(d); \
} \
\
static inline void \
M_C(name, _it)(dict_it_t it, const dict_t d) \
{ \
DICTI_OA_CONTRACT(d); \
assert (it != NULL); \
it->dict = d; \
size_t i = 0; \
while ((M_CALL_OOR_EQUAL(key_oplist, d->data[i].key, DICTI_OA_EMPTY) \
|| M_CALL_OOR_EQUAL(key_oplist, d->data[i].key, DICTI_OA_DELETED)) \
&& i <= d->mask) { \
i++; \
} \
it->index = i; \
} \
\
static inline void \
M_C(name, _it_set)(dict_it_t it, const dict_it_t ref) \
{ \
assert (it != NULL); \
assert (ref != NULL); \
it->dict = ref->dict; \
it->index = ref->index; \
DICTI_OA_CONTRACT (it->dict); \
} \
\
static inline void \
M_C(name, _it_last)(dict_it_t it, const dict_t d) \
{ \
DICTI_OA_CONTRACT(d); \
assert (it != NULL); \
it->dict = d; \
size_t i = d->mask; \
while ((M_CALL_OOR_EQUAL(key_oplist, d->data[i].key, DICTI_OA_EMPTY) \
|| M_CALL_OOR_EQUAL(key_oplist, d->data[i].key, DICTI_OA_DELETED)) \
&& i <= d->mask) { \
i--; \
} \
it->index = i; \
} \
\
static inline void \
M_C(name, _it_end)(dict_it_t it, const dict_t d) \
{ \
DICTI_OA_CONTRACT(d); \
assert (it != NULL); \
it->dict = d; \
it->index = d->mask+1; \
} \
\
static inline bool \
M_C(name, _end_p)(const dict_it_t it) \
{ \
assert (it != NULL); \
DICTI_OA_CONTRACT (it->dict); \
return it->index > it->dict->mask; \
} \
\
static inline void \
M_C(name, _next)(dict_it_t it) \
{ \
assert (it != NULL); \
DICTI_OA_CONTRACT (it->dict); \
size_t i = it->index + 1; \
while (i <= it->dict->mask && \
(M_CALL_OOR_EQUAL(key_oplist, it->dict->data[i].key, DICTI_OA_EMPTY) \
|| M_CALL_OOR_EQUAL(key_oplist, it->dict->data[i].key, DICTI_OA_DELETED))) { \
i++; \
} \
it->index = i; \
} \
\
static inline void \
M_C(name, _previous)(dict_it_t it) \
{ \
assert (it != NULL); \
DICTI_OA_CONTRACT (it->dict); \
/* if index was 0, the operation will overflow, and stops the loop */ \
size_t i = it->index - 1; \
while (i <= it->dict->mask && \
(M_CALL_OOR_EQUAL(key_oplist, it->dict->data[i].key, DICTI_OA_EMPTY) \
|| M_CALL_OOR_EQUAL(key_oplist, it->dict->data[i].key, DICTI_OA_DELETED))) { \
i--; \
} \
it->index = i; \
} \
\
static inline bool \
M_C(name, _last_p)(const dict_it_t it) \
{ \
assert (it != NULL); \
dict_it_t it2; \
M_C(name,_it_set)(it2, it); \
M_C(name, _next)(it2); \
return M_C(name, _end_p)(it2); \
} \
\
static inline bool \
M_C(name, _it_equal_p)(const dict_it_t it1,const dict_it_t it2) \
{ \
assert (it1 != NULL && it2 != NULL); \
DICTI_OA_CONTRACT (it1->dict); \
DICTI_OA_CONTRACT (it2->dict); \
return it1->dict == it2->dict && it1->index == it2->index; \
} \
\
static inline it_deref_t * \
M_C(name, _ref)(const dict_it_t it) \
{ \
assert (it != NULL); \
DICTI_OA_CONTRACT (it -> dict); \
const size_t i = it->index; \
assert (i <= it->dict->mask); \
assert (!M_CALL_OOR_EQUAL(key_oplist, it->dict->data[i].key, DICTI_OA_EMPTY)); \
assert (!M_CALL_OOR_EQUAL(key_oplist, it->dict->data[i].key, DICTI_OA_DELETED)); \
return &it->dict->data[i] M_IF(isSet)(.key, ); \
} \
\
static inline const it_deref_t * \
M_C(name, _cref)(const dict_it_t it) \
{ \
return M_CONST_CAST(it_deref_t, M_C(name, _ref)(it)); \
} \
\
static inline void \
M_C(name,_reserve)(dict_t dict, size_t capacity) \
{ \
DICTI_OA_CONTRACT(dict); \
size_t size; \
/* Get the size which will allow to fit this capacity \
NOTE: Stricly speaking we need to perform a round up to ensure \
that no reallocation of the hash map occurs up to capacity */ \
size = (size_t) m_core_roundpow2 ((uint64_t) ((double) capacity * (1.0 / coeff_up))); \
/* Test for overflow of the computation */ \
if (M_UNLIKELY (size < capacity)) { \
M_MEMORY_FULL((size_t)-1); \
} \
assert (M_POWEROF2_P(size)); \
if (size > dict->mask+1) { \
dict->upper_limit = (size_t) ((double) size * coeff_up) - 1; \
M_C(name,_int_resize_up)(dict, size, false); \
} \
DICTI_OA_CONTRACT(dict); \
} \
\
M_IF_METHOD(EQUAL, value_oplist)( \
static inline bool \
M_C(name, _equal_p)(const dict_t dict1, const dict_t dict2) \
{ \
DICTI_OA_CONTRACT(dict1); \
DICTI_OA_CONTRACT(dict2); \
/* NOTE: Key type has mandatory equal operator */ \
/* Easy case */ \
if (M_LIKELY (dict1->count != dict2->count)) \
return false; \
if (M_UNLIKELY (dict1->count == 0)) \
return true; \
/* Otherwise this is the slow path */ \
dict_it_t it; \
for(M_C(name, _it)(it, dict1) ; \
!M_C(name, _end_p)(it); \
M_C(name, _next)(it)) { \
const it_deref_t *item = M_C(name, _cref)(it); \
value_type *ptr = M_C(name, _get)(dict2, M_IF(isSet)(*item, item->key)); \
if (ptr == NULL) \
return false; \
if (M_CALL_EQUAL(value_oplist, item->value, *ptr) == false) \
return false; \
} \
return true; \
} \
, /* no value equal */ ) \
\
DICTI_FUNC_ADDITIONAL_DEF2(name, key_type, key_oplist, value_type, value_oplist, isSet, dict_t, dict_it_t, it_deref_t)
#endif