diff --git a/hash.c b/hash.c
index 91a4e9f..8240659 100644
--- a/hash.c
+++ b/hash.c
@@ -222,8 +222,18 @@ hash_foreach_call(VALUE arg)
return Qnil;
}
-void
-rb_hash_foreach(VALUE hash, int (*func)(ANYARGS), VALUE farg)
+static VALUE
+hash_reverse_foreach_call(VALUE arg)
+{
+ VALUE hash = ((struct hash_foreach_arg *)arg)->hash;
+ if (st_reverse_foreach_check(RHASH(hash)->ntbl, hash_foreach_iter, (st_data_t)arg, (st_data_t)Qundef)) {
+ rb_raise(rb_eRuntimeError, "hash modified during iteration");
+ }
+ return Qnil;
+}
+
+static void
+do_hash_foreach(VALUE hash, int (*func)(ANYARGS), VALUE farg, int reverse)
{
struct hash_foreach_arg arg;
@@ -233,7 +243,22 @@ rb_hash_foreach(VALUE hash, int (*func)(ANYARGS), VALUE farg)
arg.hash = hash;
arg.func = (rb_foreach_func *)func;
arg.arg = farg;
- rb_ensure(hash_foreach_call, (VALUE)&arg, hash_foreach_ensure, hash);
+ if (reverse)
+ rb_ensure(hash_reverse_foreach_call, (VALUE)&arg, hash_foreach_ensure, hash);
+ else
+ rb_ensure(hash_foreach_call, (VALUE)&arg, hash_foreach_ensure, hash);
+}
+
+void
+rb_hash_foreach(VALUE hash, int (*func)(ANYARGS), VALUE farg)
+{
+ do_hash_foreach(hash, func, farg, 0);
+}
+
+static void
+rb_hash_reverse_foreach(VALUE hash, int (*func)(ANYARGS), VALUE farg)
+{
+ do_hash_foreach(hash, func, farg, 1);
}
static VALUE
@@ -1534,6 +1559,37 @@ rb_hash_each_pair(VALUE hash)
return hash;
}
+/*
+ * call-seq:
+ * hsh.reverse_each {| key, value | block } -> hsh
+ * hsh.reverse_each -> an_enumerator
+ *
+ * Calls block once for each key in hsh, passing the key-value
+ * pair as parameters.
+ *
+ * If no block is given, an enumerator is returned instead.
+ *
+ * h = { "a" => 100, "b" => 200 }
+ * h.reverse_each {|key, value| puts "#{key} is #{value}" }
+ *
+ * produces:
+ *
+ * b is 200
+ * a is 100
+ *
+ */
+
+static VALUE
+rb_hash_reverse_each_pair(VALUE hash)
+{
+ RETURN_SIZED_ENUMERATOR(hash, 0, 0, hash_enum_size);
+ if (rb_block_arity() > 1)
+ rb_hash_reverse_foreach(hash, each_pair_i_fast, 0);
+ else
+ rb_hash_reverse_foreach(hash, each_pair_i, 0);
+ return hash;
+}
+
static int
to_a_i(VALUE key, VALUE value, VALUE ary)
{
@@ -3643,6 +3699,7 @@ Init_Hash(void)
rb_define_method(rb_cHash,"each_key", rb_hash_each_key, 0);
rb_define_method(rb_cHash,"each_pair", rb_hash_each_pair, 0);
rb_define_method(rb_cHash,"each", rb_hash_each_pair, 0);
+ rb_define_method(rb_cHash,"reverse_each", rb_hash_reverse_each_pair, 0);
rb_define_method(rb_cHash,"keys", rb_hash_keys, 0);
rb_define_method(rb_cHash,"values", rb_hash_values, 0);
diff --git a/include/ruby/st.h b/include/ruby/st.h
index e71301b..8f2a2f3 100644
--- a/include/ruby/st.h
+++ b/include/ruby/st.h
@@ -111,7 +111,7 @@ typedef int st_update_callback_func(st_data_t *key, st_data_t *value, st_data_t
int st_update(st_table *table, st_data_t key, st_update_callback_func *func, st_data_t arg);
int st_foreach(st_table *, int (*)(ANYARGS), st_data_t);
int st_foreach_check(st_table *, int (*)(ANYARGS), st_data_t, st_data_t);
-int st_reverse_foreach(st_table *, int (*)(ANYARGS), st_data_t);
+int st_reverse_foreach_check(st_table *, int (*)(ANYARGS), st_data_t, st_data_t);
void st_add_direct(st_table *, st_data_t, st_data_t);
void st_free_table(st_table *);
void st_cleanup_safe(st_table *, st_data_t);
diff --git a/st.c b/st.c
index 6e3df62..5a2aef6 100644
--- a/st.c
+++ b/st.c
@@ -1091,54 +1091,67 @@ st_foreach(st_table *table, int (*func)(ANYARGS), st_data_t arg)
return 0;
}
-#if 0 /* unused right now */
int
-st_reverse_foreach(st_table *table, int (*func)(ANYARGS), st_data_t arg)
+st_reverse_foreach_check(st_table *table, int (*func)(ANYARGS), st_data_t arg, st_data_t never)
{
st_table_entry *ptr, **last, *tmp;
enum st_retval retval;
int i;
if (table->entries_packed) {
- for (i = table->num_entries-1; 0 <= i; i--) {
- int j;
- st_data_t key, val;
- key = PKEY(table, i);
- val = PVAL(table, i);
- retval = (*func)(key, val, arg);
- switch (retval) {
+ for (i = table->num_entries-1; i >= 0; i--) {
+ st_data_t key, val;
+ st_index_t hash;
+ key = PKEY(table, i);
+ val = PVAL(table, i);
+ hash = PHASH(table, i);
+ if (key == never) continue;
+ retval = (*func)(key, val, arg);
+ if (!table->entries_packed) {
+ FIND_ENTRY(table, ptr, hash, i);
+ if (retval == ST_CHECK) {
+ if (!ptr) goto deleted;
+ goto unpacked_continue;
+ }
+ goto unpacked;
+ }
+ switch (retval) {
case ST_CHECK: /* check if hash is modified during iteration */
- for (j = 0; j < table->num_entries; j++) {
- if (PKEY(table, j) == key)
- break;
- }
- if (j == table->num_entries) {
- /* call func with error notice */
- retval = (*func)(0, 0, arg, 1);
- return 1;
- }
+ if (PHASH(table, i) == 0 && PKEY(table, i) == never) {
+ break;
+ }
+ i = find_packed_index(table, hash, key);
+ if (i == table->real_entries) {
+ goto deleted;
+ }
/* fall through */
case ST_CONTINUE:
break;
case ST_STOP:
return 0;
case ST_DELETE:
- remove_packed_entry(table, i);
- break;
- }
+ remove_safe_packed_entry(table, i, never);
+ break;
+ }
}
return 0;
}
+ else {
+ ptr = table->tail;
+ }
- if ((ptr = table->head) != 0) {
- ptr = ptr->back;
+ if (ptr != 0) {
do {
- retval = (*func)(ptr->key, ptr->record, arg, 0);
+ if (ptr->key == never)
+ goto unpacked_continue;
+ i = ptr->hash % table->num_bins;
+ retval = (*func)(ptr->key, ptr->record, arg);
+ unpacked:
switch (retval) {
case ST_CHECK: /* check if hash is modified during iteration */
- i = ptr->hash % table->num_bins;
for (tmp = table->bins[i]; tmp != ptr; tmp = tmp->next) {
if (!tmp) {
+ deleted:
/* call func with error notice */
retval = (*func)(0, 0, arg, 1);
return 1;
@@ -1146,6 +1159,7 @@ st_reverse_foreach(st_table *table, int (*func)(ANYARGS), st_data_t arg)
}
/* fall through */
case ST_CONTINUE:
+ unpacked_continue:
ptr = ptr->back;
break;
case ST_STOP:
@@ -1155,22 +1169,18 @@ st_reverse_foreach(st_table *table, int (*func)(ANYARGS), st_data_t arg)
for (; (tmp = *last) != 0; last = &tmp->next) {
if (ptr == tmp) {
tmp = ptr->back;
- *last = ptr->next;
remove_entry(table, ptr);
- st_free_entry(ptr);
+ ptr->key = ptr->record = never;
+ ptr->hash = 0;
ptr = tmp;
break;
}
}
- ptr = ptr->next;
- free(tmp);
- table->num_entries--;
}
} while (ptr && table->head);
}
return 0;
}
-#endif
/*
* hash_32 - 32 bit Fowler/Noll/Vo FNV-1a hash code
diff --git a/test/ruby/test_hash.rb b/test/ruby/test_hash.rb
index 21fbf41..685a969 100644
--- a/test/ruby/test_hash.rb
+++ b/test/ruby/test_hash.rb
@@ -400,6 +400,23 @@ class TestHash < Test::Unit::TestCase
assert_equal([], res - expected)
end
+ def test_reverse_each
+ count = 0
+ @cls[].reverse_each { |k, v| count + 1 }
+ assert_equal(0, count)
+
+ h = @h
+ h.reverse_each do |k, v|
+ assert_equal(v, h.delete(k))
+ end
+ assert_equal(@cls[], h)
+
+ h = @cls[]
+ h[1] = 1
+ h[2] = 2
+ assert_equal([[2,2],[1,1]], h.reverse_each.to_a)
+ end
+
def test_empty?
assert_empty(@cls[])
assert_not_empty(@h)