Skip to content

Commit

Permalink
Add POTTERY_FOR_EACH()
Browse files Browse the repository at this point in the history
  • Loading branch information
ludocode committed Feb 14, 2021
1 parent fb30831 commit bf6a647
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 29 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@ person_t** eve = person_map_find(&map, "eve");
if (!person_map_entry_exists(&map, eve)) {
// not found!
}
// for-each loop works on any container
person_t** ref;
POTTERY_FOR_EACH(ref, person_map, &map) {
printf("%s\n", (*ref)->name);
}
```

See the full example [here](examples/pottery/person_map/).
Expand Down
27 changes: 14 additions & 13 deletions examples/pottery/person_map/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -101,23 +101,24 @@ int main(void) {
person_map_insert(&map, person_new("dave", 57));


// Print the contents of the map
printf("Map contains:\n");
person_t** ref;
POTTERY_FOR_EACH(ref, person_map, &map) {
printf(" %s\n", (*ref)->name);
}


// Find alice
person_t** entry = person_map_find(&map, "alice");
printf("%s is %i years old.\n", (*entry)->name, (*entry)->age);
ref = person_map_find(&map, "alice");
printf("%s is %i years old.\n", (*ref)->name, (*ref)->age);

// Remove alice from the map. The map deletes alice.
person_map_remove(&map, entry);
person_map_remove(&map, ref);


// Find bob, checking whether he was found
entry = person_map_find(&map, "bob");
if (!person_map_entry_exists(&map, entry)) {
fprintf(stderr, "bob not found!\n");
abort();
}

// Extract bob from the map. This returns bob without deleting him.
person_t* bob = person_map_extract(&map, entry);
person_t* bob = person_map_extract(&map, person_map_find(&map, "bob"));

// We now own bob; he is no longer in the map. We delete him when done.
printf("%s is %i years old.\n", bob->name, bob->age);
Expand All @@ -131,8 +132,8 @@ int main(void) {


// Try to find eve. Eve should not exist.
entry = person_map_find(&map, "eve");
if (person_map_entry_exists(&map, entry)) {
ref = person_map_find(&map, "eve");
if (person_map_entry_exists(&map, ref)) {
fprintf(stderr, "eve does not belong in the map!\n");
abort();
}
Expand Down
97 changes: 97 additions & 0 deletions include/pottery/platform/pottery_platform_features.h
Original file line number Diff line number Diff line change
Expand Up @@ -222,4 +222,101 @@ typedef int pottery_error_t;



// This doesn't really belong in "platform" but we don't have a better place
// for this yet. These headers need to be moved around, probably renaming
// "platform" to "common".

/*
* POTTERY_FOR_EACH()
*
* The for_each loop loops over all entries in the container. It starts with
* prefix##_begin() and loops with prefix##_next() until
* prefix##_entry_exists() returns false, assigning prefix##_entry_ref() to
* your ref in each iteration of the loop.
*
* @param ref An l-value of type prefix##_ref_t (which is a pointer to value
* for concrete value types.) This will be assigned in each loop
* iteration.
* @param prefix The pottery PREFIX of the container.
* @param container A pointer to the container.
*
* @warning The ref and container arguments may be evaluated multiple times.
*
* @note In ANSI C/gnu89 (i.e. pre-C99), POTTERY_FOR_EACH() only supports
* containers where the ref and entry types are the same because an entry
* cannot be defined within the for loop. Usage is the same where
* supported so POTTERY_FOR_EACH() in gnu89 is forward-compatible.
*/

// First determine whether we can suppor the full implementation of for-each
// (we need support for C99/C++-style definitions in for loops)

#ifndef POTTERY_HAS_FULL_FOR_EACH
#ifdef __STDC_VERSION__
#if __STDC_VERSION__ >= 199901L
#define POTTERY_HAS_FULL_FOR_EACH 1
#endif
#endif
#endif

#ifndef POTTERY_HAS_FULL_FOR_EACH
#ifdef __cplusplus
#define POTTERY_HAS_FULL_FOR_EACH 1
#endif
#endif

#ifndef POTTERY_HAS_FULL_FOR_EACH
#define POTTERY_HAS_FULL_FOR_EACH 0
#endif

// If possible, POTTERY_FOR_EACH() defines an entry iterator and performs
// correct ref conversions. This means it works on any container type.

#ifndef POTTERY_FOR_EACH
#if POTTERY_HAS_FULL_FOR_EACH
#define POTTERY_FOR_EACH(ref, prefix, container) \
for (POTTERY_CONCAT(prefix, _entry_t) POTTERY_CONCAT(prefix, _for_each_entry) = \
POTTERY_CONCAT(prefix, _begin)((container)); \
(POTTERY_CONCAT(prefix, _entry_exists)((container), POTTERY_CONCAT(prefix, _for_each_entry)) ? \
(((ref) = POTTERY_CONCAT(prefix, _entry_ref)((container), POTTERY_CONCAT(prefix, _for_each_entry))), true) : \
false); \
POTTERY_CONCAT(prefix, _for_each_entry) = \
POTTERY_CONCAT(prefix, _next)((container), POTTERY_CONCAT(prefix, _for_each_entry)))
#endif
#endif

// POTTERY_FOR_EACH() cannot declare an entry iterator pre-C99 so in this case
// it only works on containers where the entry and ref types are the same.

// If possible we add a static assert to ensure that the types are the same
// (otherwise if e.g. the ref type is void* and the entry type is some other
// pointer, they are implicitly convertible so this would silently compile to
// nonsense.) (This causes warnings if you're compiling under gnu89 with
// -Wpedantic since ANSI C doesn't have statement expressions or _Static_assert,
// but so does everything else in Pottery like these // comments.)
#ifndef POTTERY_FOR_EACH
#if defined(__GNUC__) && defined(__has_builtin)
#if __has_builtin(__builtin_types_compatible_p)
#define POTTERY_FOR_EACH(ref, prefix, container) \
for ((ref) = ({ \
_Static_assert(__builtin_types_compatible_p(POTTERY_CONCAT(prefix, _entry_t), POTTERY_CONCAT(prefix, _ref_t)), \
"Entry and ref types do not match! POTTERY_FOR_EACH() cannot be used under gnu89 on this container."); \
POTTERY_CONCAT(prefix, _begin)((container)); \
}); \
POTTERY_CONCAT(prefix, _entry_exists)((container), (ref)); \
(ref) = POTTERY_CONCAT(prefix, _next)((container), (ref)))
#endif
#endif
#endif

// This is the same as the previous one, just without the static assert.
#ifndef POTTERY_FOR_EACH
#define POTTERY_FOR_EACH(ref, prefix, container) \
for ((ref) = POTTERY_CONCAT(prefix, _begin)((container)); \
POTTERY_CONCAT(prefix, _entry_exists)((container), (ref)); \
(ref) = POTTERY_CONCAT(prefix, _next)((container), (ref)))
#endif



#endif
2 changes: 2 additions & 0 deletions include/pottery/vector/impl/pottery_vector_declarations.t.h
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ pottery_error_t pottery_vector_shrink(pottery_vector_t* vector);
static inline
ptrdiff_t pottery_vector_offset(pottery_vector_t* vector, pottery_vector_entry_t first,
pottery_vector_entry_t second) {
(void)vector;
pottery_assert(first >= pottery_vector_begin(vector) && first <= pottery_vector_end(vector));
pottery_assert(second >= pottery_vector_begin(vector) && second <= pottery_vector_end(vector));
return pottery_cast(ptrdiff_t, second - first);
Expand All @@ -213,6 +214,7 @@ ptrdiff_t pottery_vector_offset(pottery_vector_t* vector, pottery_vector_entry_t
*/
static inline
pottery_vector_entry_t pottery_vector_shift(pottery_vector_t* vector, pottery_vector_entry_t entry, ptrdiff_t offset) {
(void)vector;
pottery_assert(entry >= pottery_vector_begin(vector) && entry <= pottery_vector_end(vector));
entry += offset;
pottery_assert(entry >= pottery_vector_begin(vector) && entry <= pottery_vector_end(vector));
Expand Down
74 changes: 58 additions & 16 deletions test/src/pottery/unit/map/test_pottery_unit_map_ufo.t.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,6 @@ static inline void test_ufo_map_init(ufo_map_t* map) {
#endif
}

POTTERY_TEST_MAP_UFO(init_destroy) {
ufo_map_t map;
test_ufo_map_init(&map);
pottery_test_assert(ufo_map_count(&map) == 0);
ufo_map_destroy(&map);
}

static inline void check_ufo_map(ufo_map_t* map) {
size_t count = 0;
ufo_map_entry_t entry = ufo_map_begin(map);
Expand All @@ -72,6 +65,27 @@ static inline void check_ufo_map(ufo_map_t* map) {
pottery_test_assert(count == ufo_map_count(map));
}

static inline void test_ufo_map_destroy(ufo_map_t* map) {
#if POTTERY_TEST_MAP_UFO_NO_DESTROY
// We have to destroy and displace all manually.
ufo_t* ufo;
POTTERY_FOR_EACH(ufo, ufo_map, map)
ufo_destroy(ufo);
ufo_map_displace_all(map);
#endif

check_ufo_map(map);

ufo_map_destroy(map);
}

POTTERY_TEST_MAP_UFO(init_destroy) {
ufo_map_t map;
test_ufo_map_init(&map);
pottery_test_assert(ufo_map_count(&map) == 0);
ufo_map_destroy(&map);
}

POTTERY_TEST_MAP_UFO(remove) {
ufo_map_t map;
test_ufo_map_init(&map);
Expand Down Expand Up @@ -140,19 +154,12 @@ POTTERY_TEST_MAP_UFO(remove_all) {

check_ufo_map(&map);

#if POTTERY_TEST_MAP_UFO_NO_DESTROY
// Destroy and displace all
for (entry = ufo_map_begin(&map); ufo_map_entry_exists(&map, entry); entry = ufo_map_next(&map, entry))
ufo_destroy(entry);
ufo_map_displace_all(&map);
#else
#if !POTTERY_TEST_MAP_UFO_NO_DESTROY
// Remove all destroys all elements.
ufo_map_remove_all(&map);
#endif

check_ufo_map(&map);

ufo_map_destroy(&map);
test_ufo_map_destroy(&map);
}

POTTERY_TEST_MAP_UFO(grow_and_shrink) {
Expand Down Expand Up @@ -228,6 +235,41 @@ POTTERY_TEST_MAP_UFO(grow_and_shrink) {
ufo_map_destroy(&map);
}

#if POTTERY_HAS_FULL_FOR_EACH
POTTERY_TEST_MAP_UFO(for_each) {
ufo_map_t map;
test_ufo_map_init(&map);

for (int i = 0; i < 32; ++i) {

// test that POTTERY_FOR_EACH() visits all elements
// We could improve this later to verify that they are in order for
// ordered containers
int count = 0;
ufo_t* ref;
POTTERY_FOR_EACH(ref, ufo_map, &map) {
pottery_test_assert(ref != pottery_null);
++count;
}
pottery_test_assert(count == i);
pottery_test_assert(count == pottery_cast(int, ufo_map_count(&map)));

// add an element
char key[16];
snprintf(key, sizeof(key), "%i", i);
bool created = false;
ufo_map_entry_t entry;
pottery_test_assert(ufo_map_emplace_key(&map, key, &entry, &created) == POTTERY_OK);
pottery_test_assert(created);
ufo_init(entry, key, i);
check_ufo_map(&map);

}

test_ufo_map_destroy(&map);
}
#endif

#undef POTTERY_TEST_MAP_UFO

#undef POTTERY_TEST_MAP_UFO_HAS_CAPACITY
Expand Down
27 changes: 27 additions & 0 deletions test/src/pottery/unit/test_pottery_pager.c
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,30 @@ POTTERY_TEST(pottery_pager_bulk_extract_last) {

int_pager_destroy(&pager);
}

// This should be moved to test_pottery_unit_array_ufo but pager doesn't use it
// yet! See for_each in test_pottery_unit_map_ufo for the map equivalent
#if POTTERY_HAS_FULL_FOR_EACH
POTTERY_TEST(pottery_pager_for_ecah) {
int_pager_t pager;
int_pager_init(&pager);

for (int i = 0; i < 32; ++i) {

// test that POTTERY_FOR_EACH() visits all elements in the correct order
int count = 0;
int* ref;
POTTERY_FOR_EACH(ref, int_pager, &pager) {
pottery_test_assert(*ref == count);
++count;
}

// test that we visited all elements
pottery_test_assert(count == i);
pottery_test_assert(count == pottery_cast(int, int_pager_count(&pager)));

// add an element
int_pager_insert_last(&pager, i);
}
}
#endif

0 comments on commit bf6a647

Please sign in to comment.