Skip to content

Commit fe02ed7

Browse files
committed
Implementing hash iterator.
`mrb_hash_foreach`, provides a foreach implementation on hashes using callbacks. Although sufficient for ruby code, some C and C++ idioms can be made much simpler and easier with a good old `for` loop. This pull request implements an iterator that allows user-controlled loops over hash elements in C code, as follows: ``` for (mrb_hash_iterator i = mrb_hash_iterator_new(h); mrb_hash_iterator_remaining(&i); mrb_hash_iterator_move_next(&i) ) { mrb_value key = mrb_hash_iterator_key(&i); mrb_value value = mrb_hash_iterator_value(&i); /* ... */ } ``` The code is comprised of `struct mrb_hash_iterator`, 4 accompanying functions and 1 macro with inline documentation. ``` MRB_API mrb_hash_iterator mrb_hash_iterator_new(struct RHash *h); MRB_API mrb_bool mrb_hash_iterator_move_next(mrb_hash_iterator *it); MRB_API mrb_value mrb_hash_iterator_key(mrb_hash_iterator *it); MRB_API mrb_value mrb_hash_iterator_value(mrb_hash_iterator *it); /* macro */ uint32_t mrb_hash_iterator_remaining(mrb_hash_iterator *it); ```
1 parent 96cf9ba commit fe02ed7

File tree

3 files changed

+239
-0
lines changed

3 files changed

+239
-0
lines changed

include/mruby/hash.h

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,47 @@ MRB_API void mrb_hash_merge(mrb_state *mrb, mrb_value hash1, mrb_value hash2);
229229
typedef int (mrb_hash_foreach_func)(mrb_state *mrb, mrb_value key, mrb_value val, void *data);
230230
MRB_API void mrb_hash_foreach(mrb_state *mrb, struct RHash *hash, mrb_hash_foreach_func *func, void *p);
231231

232+
typedef struct mrb_hash_iterator {
233+
struct hash_entry *entry;
234+
uint32_t rem;
235+
} mrb_hash_iterator;
236+
237+
/*
238+
* Returns an iterator for the given hash.
239+
*
240+
* @param h The target hash.
241+
*/
242+
MRB_API mrb_hash_iterator mrb_hash_iterator_new(struct RHash *h);
243+
244+
/*
245+
* Moves to the next element in the iterator.
246+
* Returns false if no more elements.
247+
*
248+
* @param it Pointer to the hash iterator.
249+
*/
250+
MRB_API mrb_bool mrb_hash_iterator_move_next(mrb_hash_iterator *it);
251+
252+
/*
253+
* Returns the number of items left in the iterator.
254+
*
255+
* @param it Pointer to the hash iterator.
256+
*/
257+
#define mrb_hash_iterator_remaining(it) ((it)->rem)
258+
259+
/*
260+
* Returns the key for the current hash element of the iterator.
261+
*
262+
* @param it Pointer to the hash iterator.
263+
*/
264+
MRB_API mrb_value mrb_hash_iterator_key(mrb_hash_iterator *it);
265+
266+
/*
267+
* Returns the value for the current hash element of the iterator.
268+
*
269+
* @param it Pointer to the hash iterator.
270+
*/
271+
MRB_API mrb_value mrb_hash_iterator_value(mrb_hash_iterator *it);
272+
232273
MRB_END_DECL
233274

234275
#endif /* MRUBY_HASH_H */

src/hash.c

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1111,6 +1111,52 @@ mrb_hash_foreach(mrb_state *mrb, struct RHash *h, mrb_hash_foreach_func *func, v
11111111
});
11121112
}
11131113

1114+
MRB_API mrb_hash_iterator
1115+
mrb_hash_iterator_new(struct RHash *h)
1116+
{
1117+
mrb_hash_iterator it;
1118+
1119+
if (h_ar_p(h)) {
1120+
it.entry = ar_ea(h);
1121+
it.rem = ar_size(h);
1122+
} else {
1123+
it.entry = ht_ea(h);
1124+
it.rem = ht_size(h);
1125+
}
1126+
1127+
if (0 < it.rem) {
1128+
while (entry_deleted_p(it.entry)) {
1129+
++it.entry;
1130+
}
1131+
}
1132+
1133+
return it;
1134+
}
1135+
1136+
MRB_API mrb_bool
1137+
mrb_hash_iterator_move_next(mrb_hash_iterator *it)
1138+
{
1139+
if (0 < it->rem) {
1140+
while (entry_deleted_p(++it->entry));
1141+
--it->rem;
1142+
return it->rem > 0;
1143+
}
1144+
1145+
return FALSE;
1146+
}
1147+
1148+
MRB_API mrb_value
1149+
mrb_hash_iterator_key(mrb_hash_iterator *it)
1150+
{
1151+
return it->entry->key;
1152+
}
1153+
1154+
MRB_API mrb_value
1155+
mrb_hash_iterator_value(mrb_hash_iterator *it)
1156+
{
1157+
return it->entry->val;
1158+
}
1159+
11141160
MRB_API mrb_value
11151161
mrb_hash_new(mrb_state *mrb)
11161162
{

test/t/hash_iterator.c

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
#include <mruby.h>
2+
#include <mruby/compile.h>
3+
#include <mruby/hash.h>
4+
#include <mruby/value.h>
5+
6+
#include <assert.h>
7+
#include <stdlib.h>
8+
9+
#define CHECK(...) do { \
10+
mrb_bool result = (__VA_ARGS__); \
11+
assert(result && (#__VA_ARGS__)); \
12+
if (!result) { return EXIT_FAILURE; } \
13+
} while (0)
14+
15+
#define CHECK_EQ(Expected, ...) \
16+
CHECK(mrb_eql(mrb, (Expected), (__VA_ARGS__)))
17+
18+
int test1() {
19+
mrb_state *mrb = mrb_open();
20+
CHECK(mrb);
21+
22+
int checkpoint = mrb_gc_arena_save(mrb);
23+
mrb_value hash = mrb_load_string(mrb, "{a: 10, b: 20, c: 30, d: 40, e: 50}");
24+
mrb_gc_arena_restore(mrb, checkpoint);
25+
26+
mrb_value a = mrb_symbol_value(mrb_intern_cstr(mrb, "a"));
27+
mrb_value b = mrb_symbol_value(mrb_intern_cstr(mrb, "b"));
28+
mrb_value c = mrb_symbol_value(mrb_intern_cstr(mrb, "c"));
29+
mrb_value d = mrb_symbol_value(mrb_intern_cstr(mrb, "d"));
30+
mrb_value e = mrb_symbol_value(mrb_intern_cstr(mrb, "e"));
31+
32+
mrb_value i10 = mrb_int_value(mrb, 10);
33+
mrb_value i20 = mrb_int_value(mrb, 20);
34+
mrb_value i30 = mrb_int_value(mrb, 30);
35+
mrb_value i40 = mrb_int_value(mrb, 40);
36+
mrb_value i50 = mrb_int_value(mrb, 50);
37+
38+
mrb_hash_iterator iterator = mrb_hash_iterator_new(mrb_hash_ptr(hash));
39+
40+
CHECK(mrb_hash_iterator_remaining(&iterator) == 5);
41+
CHECK_EQ(a, mrb_hash_iterator_key(&iterator));
42+
CHECK_EQ(i10, mrb_hash_iterator_value(&iterator));
43+
44+
CHECK(mrb_hash_iterator_move_next(&iterator));
45+
CHECK(mrb_hash_iterator_remaining(&iterator) == 4);
46+
CHECK_EQ(b, mrb_hash_iterator_key(&iterator));
47+
CHECK_EQ(i20, mrb_hash_iterator_value(&iterator));
48+
49+
CHECK(mrb_hash_iterator_move_next(&iterator));
50+
CHECK(mrb_hash_iterator_remaining(&iterator) == 3);
51+
CHECK_EQ(c, mrb_hash_iterator_key(&iterator));
52+
CHECK_EQ(i30, mrb_hash_iterator_value(&iterator));
53+
54+
CHECK(mrb_hash_iterator_move_next(&iterator));
55+
CHECK(mrb_hash_iterator_remaining(&iterator) == 2);
56+
CHECK_EQ(d, mrb_hash_iterator_key(&iterator));
57+
CHECK_EQ(i40, mrb_hash_iterator_value(&iterator));
58+
59+
CHECK(mrb_hash_iterator_move_next(&iterator));
60+
CHECK(mrb_hash_iterator_remaining(&iterator) == 1);
61+
CHECK_EQ(e, mrb_hash_iterator_key(&iterator));
62+
CHECK_EQ(i50, mrb_hash_iterator_value(&iterator));
63+
64+
CHECK(!mrb_hash_iterator_move_next(&iterator));
65+
CHECK(mrb_hash_iterator_remaining(&iterator) == 0);
66+
67+
return EXIT_SUCCESS;
68+
}
69+
70+
int test2() {
71+
mrb_state *mrb = mrb_open();
72+
CHECK(mrb);
73+
74+
int checkpoint = mrb_gc_arena_save(mrb);
75+
mrb_value hash = mrb_load_string(mrb,
76+
"x = {a: 10, b: 20, c: 30, d: 40, e: 50}\n"
77+
"x.delete(:c)\n"
78+
"x"
79+
);
80+
mrb_gc_arena_restore(mrb, checkpoint);
81+
82+
mrb_value a = mrb_symbol_value(mrb_intern_cstr(mrb, "a"));
83+
mrb_value b = mrb_symbol_value(mrb_intern_cstr(mrb, "b"));
84+
mrb_value d = mrb_symbol_value(mrb_intern_cstr(mrb, "d"));
85+
mrb_value e = mrb_symbol_value(mrb_intern_cstr(mrb, "e"));
86+
87+
mrb_value i10 = mrb_int_value(mrb, 10);
88+
mrb_value i20 = mrb_int_value(mrb, 20);
89+
mrb_value i40 = mrb_int_value(mrb, 40);
90+
mrb_value i50 = mrb_int_value(mrb, 50);
91+
92+
mrb_hash_iterator iterator = mrb_hash_iterator_new(mrb_hash_ptr(hash));
93+
94+
CHECK(mrb_hash_iterator_remaining(&iterator) == 4);
95+
CHECK_EQ(a, mrb_hash_iterator_key(&iterator));
96+
CHECK_EQ(i10, mrb_hash_iterator_value(&iterator));
97+
98+
CHECK(mrb_hash_iterator_move_next(&iterator));
99+
CHECK(mrb_hash_iterator_remaining(&iterator) == 3);
100+
CHECK_EQ(b, mrb_hash_iterator_key(&iterator));
101+
CHECK_EQ(i20, mrb_hash_iterator_value(&iterator));
102+
103+
CHECK(mrb_hash_iterator_move_next(&iterator));
104+
CHECK(mrb_hash_iterator_remaining(&iterator) == 2);
105+
CHECK_EQ(d, mrb_hash_iterator_key(&iterator));
106+
CHECK_EQ(i40, mrb_hash_iterator_value(&iterator));
107+
108+
CHECK(mrb_hash_iterator_move_next(&iterator));
109+
CHECK(mrb_hash_iterator_remaining(&iterator) == 1);
110+
CHECK_EQ(e, mrb_hash_iterator_key(&iterator));
111+
CHECK_EQ(i50, mrb_hash_iterator_value(&iterator));
112+
113+
CHECK(!mrb_hash_iterator_move_next(&iterator));
114+
CHECK(mrb_hash_iterator_remaining(&iterator) == 0);
115+
116+
return EXIT_SUCCESS;
117+
}
118+
119+
int test3() {
120+
mrb_state *mrb = mrb_open();
121+
CHECK(mrb);
122+
123+
int checkpoint = mrb_gc_arena_save(mrb);
124+
mrb_value hash = mrb_load_string(mrb,
125+
"x = {a: 10, b: 20, c: 30, d: 40, e: 50}\n"
126+
"x.delete(:c)\n"
127+
"x.delete(:e)\n"
128+
"x.delete(:b)\n"
129+
"x.delete(:a)\n"
130+
"x"
131+
);
132+
mrb_gc_arena_restore(mrb, checkpoint);
133+
134+
mrb_value d = mrb_symbol_value(mrb_intern_cstr(mrb, "d"));
135+
136+
mrb_value i40 = mrb_int_value(mrb, 40);
137+
138+
mrb_hash_iterator iterator = mrb_hash_iterator_new(mrb_hash_ptr(hash));
139+
140+
CHECK(mrb_hash_iterator_remaining(&iterator) == 1);
141+
CHECK_EQ(d, mrb_hash_iterator_key(&iterator));
142+
CHECK_EQ(i40, mrb_hash_iterator_value(&iterator));
143+
144+
CHECK(!mrb_hash_iterator_move_next(&iterator));
145+
CHECK(mrb_hash_iterator_remaining(&iterator) == 0);
146+
147+
return EXIT_SUCCESS;
148+
}
149+
150+
int main() {
151+
return test1() | test2() | test3();
152+
}

0 commit comments

Comments
 (0)