summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog5
-rw-r--r--ext/-test-/st/update/extconf.rb1
-rw-r--r--ext/-test-/st/update/update.c34
-rw-r--r--include/ruby/st.h1
-rw-r--r--st.c56
-rw-r--r--test/-ext-/st/test_update.rb39
6 files changed, 136 insertions, 0 deletions
diff --git a/ChangeLog b/ChangeLog
index 03028c289a..fd8ed4dcb6 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+Tue Dec 27 22:04:27 2011 Nobuyoshi Nakada <nobu@ruby-lang.org>
+
+ * st.c (st_update): new function to lookup the given key and
+ update the value. [ruby-dev:44998]
+
Tue Dec 27 21:17:33 2011 Nobuyoshi Nakada <nobu@ruby-lang.org>
* node.h (rb_args_info): change pre_args_num and post_args_num as
diff --git a/ext/-test-/st/update/extconf.rb b/ext/-test-/st/update/extconf.rb
new file mode 100644
index 0000000000..96dbae43ab
--- /dev/null
+++ b/ext/-test-/st/update/extconf.rb
@@ -0,0 +1 @@
+create_makefile("-test-/st/update")
diff --git a/ext/-test-/st/update/update.c b/ext/-test-/st/update/update.c
new file mode 100644
index 0000000000..33db04270c
--- /dev/null
+++ b/ext/-test-/st/update/update.c
@@ -0,0 +1,34 @@
+#include <ruby.h>
+#include <ruby/st.h>
+
+static int
+update_func(st_data_t key, st_data_t *value, st_data_t arg)
+{
+ VALUE ret = rb_yield_values(2, (VALUE)key, (VALUE)*value);
+ switch (ret) {
+ case Qfalse:
+ return ST_STOP;
+ case Qnil:
+ return ST_DELETE;
+ default:
+ *value = ret;
+ return ST_CONTINUE;
+ }
+}
+
+static VALUE
+test_st_update(VALUE self, VALUE key)
+{
+ if (st_update(RHASH_TBL(self), (st_data_t)key, update_func, 0))
+ return Qtrue;
+ else
+ return Qfalse;
+}
+
+void
+Init_update(void)
+{
+ VALUE st = rb_define_class_under(rb_define_module("Bug"), "StTable", rb_cHash);
+ rb_define_method(st, "st_update", test_st_update, 1);
+}
+
diff --git a/include/ruby/st.h b/include/ruby/st.h
index 50f2a75328..ff3da2a9c2 100644
--- a/include/ruby/st.h
+++ b/include/ruby/st.h
@@ -113,6 +113,7 @@ int st_insert(st_table *, st_data_t, st_data_t);
int st_insert2(st_table *, st_data_t, st_data_t, st_data_t (*)(st_data_t));
int st_lookup(st_table *, st_data_t, st_data_t *);
int st_get_key(st_table *, st_data_t, st_data_t *);
+int st_update(st_table *table, st_data_t key, int (*func)(st_data_t key, st_data_t *value, st_data_t arg), st_data_t arg);
int st_foreach(st_table *, int (*)(ANYARGS), st_data_t);
int st_reverse_foreach(st_table *, int (*)(ANYARGS), st_data_t);
void st_add_direct(st_table *, st_data_t, st_data_t);
diff --git a/st.c b/st.c
index 0186667ef1..d95f551063 100644
--- a/st.c
+++ b/st.c
@@ -733,6 +733,62 @@ st_cleanup_safe(st_table *table, st_data_t never)
}
int
+st_update(st_table *table, st_data_t key, int (*func)(st_data_t key, st_data_t *value, st_data_t arg), st_data_t arg)
+{
+ st_index_t hash_val, bin_pos;
+ register st_table_entry *ptr, **last, *tmp;
+ st_data_t value;
+
+ if (table->entries_packed) {
+ st_index_t i;
+ for (i = 0; i < table->num_entries; i++) {
+ if ((st_data_t)table->bins[i*2] == key) {
+ value = (st_data_t)table->bins[i*2+1];
+ switch ((*func)(key, &value, arg)) {
+ case ST_CONTINUE:
+ table->bins[i*2+1] = (struct st_table_entry*)value;
+ break;
+ case ST_DELETE:
+ table->num_entries--;
+ memmove(&table->bins[i*2], &table->bins[(i+1)*2],
+ sizeof(struct st_table_entry*) * 2 * (table->num_entries-i));
+ }
+ return 1;
+ }
+ }
+ return 0;
+ }
+
+ hash_val = do_hash(key, table);
+ FIND_ENTRY(table, ptr, hash_val, bin_pos);
+
+ if (ptr == 0) {
+ return 0;
+ }
+ else {
+ value = ptr->record;
+ switch ((*func)(ptr->key, &value, arg)) {
+ case ST_CONTINUE:
+ ptr->record = value;
+ break;
+ case ST_DELETE:
+ last = &table->bins[bin_pos];
+ for (; (tmp = *last) != 0; last = &tmp->next) {
+ if (ptr == tmp) {
+ tmp = ptr->fore;
+ *last = ptr->next;
+ REMOVE_ENTRY(table, ptr);
+ free(ptr);
+ break;
+ }
+ }
+ break;
+ }
+ return 1;
+ }
+}
+
+int
st_foreach(st_table *table, int (*func)(ANYARGS), st_data_t arg)
{
st_table_entry *ptr, **last, *tmp;
diff --git a/test/-ext-/st/test_update.rb b/test/-ext-/st/test_update.rb
new file mode 100644
index 0000000000..68fee0b751
--- /dev/null
+++ b/test/-ext-/st/test_update.rb
@@ -0,0 +1,39 @@
+require 'test/unit'
+require "-test-/st/update"
+
+class Bug::StTable
+ class Test_Update < Test::Unit::TestCase
+ def setup
+ @tbl = Bug::StTable.new
+ @tbl[:a] = 1
+ @tbl[:b] = 2
+ end
+
+ def test_notfound
+ called = false
+ assert_equal(false, @tbl.st_update(:c) {called = true})
+ assert_equal(false, called)
+ end
+
+ def test_continue
+ args = nil
+ assert_equal(true, @tbl.st_update(:a) {|*x| args = x; false})
+ assert_equal({a: 1, b: 2}, @tbl, :a)
+ assert_equal([:a, 1], args)
+ end
+
+ def test_delete
+ args = nil
+ assert_equal(true, @tbl.st_update(:a) {|*x| args = x; nil})
+ assert_equal({b: 2}, @tbl, :a)
+ assert_equal([:a, 1], args)
+ end
+
+ def test_update
+ args = nil
+ assert_equal(true, @tbl.st_update(:a) {|*x| args = x; 3})
+ assert_equal({a: 3, b: 2}, @tbl, :a)
+ assert_equal([:a, 1], args)
+ end
+ end
+end