summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog9
-rw-r--r--NEWS2
-rw-r--r--array.c26
-rw-r--r--hash.c24
-rw-r--r--internal.h2
-rw-r--r--object.c47
-rw-r--r--test/ruby/test_array.rb6
-rw-r--r--test/ruby/test_hash.rb6
8 files changed, 121 insertions, 1 deletions
diff --git a/ChangeLog b/ChangeLog
index dc5a39dd60..b413bb8bd6 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+Mon Nov 9 21:27:23 2015 Nobuyoshi Nakada <nobu@ruby-lang.org>
+
+ * array.c (rb_ary_dig): new method Array#dig.
+
+ * hash.c (rb_hash_dig): new method Hash#dig.
+
+ * object.c (rb_obj_dig): dig in nested arrays/hashes.
+ [Feature #11643]
+
Mon Nov 9 18:00:47 2015 Yuki Nishijima <mail@yukinishijima.net>
* gems/bundled_gems: Upgrade the did_you_mean gem to 1.0.0.beta3
diff --git a/NEWS b/NEWS
index 77e47b0973..dc7babe03a 100644
--- a/NEWS
+++ b/NEWS
@@ -42,6 +42,7 @@ with all sufficient information, see the ChangeLog file.
* Array
* Array#bsearch_index [Feature #10730]
+ * Array#dig [Feature #11643]
* Enumerable
@@ -57,6 +58,7 @@ with all sufficient information, see the ChangeLog file.
* Hash
* Hash#fetch_values [Feature #10017]
+ * Hash#dig [Feature #11643]
* IO
diff --git a/array.c b/array.c
index 56f7664fc0..7469cc2387 100644
--- a/array.c
+++ b/array.c
@@ -1302,7 +1302,7 @@ rb_ary_aref(int argc, const VALUE *argv, VALUE ary)
* a.at(-1) #=> "e"
*/
-static VALUE
+VALUE
rb_ary_at(VALUE ary, VALUE pos)
{
return rb_ary_entry(ary, NUM2LONG(pos));
@@ -5532,6 +5532,29 @@ rb_ary_any_p(VALUE ary)
}
/*
+ * call-seq:
+ * ary.dig(idx, ...) -> object
+ *
+ * Retrieves the value object corresponding to the each <i>idx</i>
+ * objects repeatedly.
+ *
+ * a = [[1, [2, 3]]]
+ *
+ * a.dig(0, 1, 1) #=> 3
+ * a.dig(0, 0, 0) #=> nil
+ */
+
+VALUE
+rb_ary_dig(int argc, VALUE *argv, VALUE self)
+{
+ rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
+ self = rb_ary_at(self, *argv);
+ if (!--argc) return self;
+ ++argv;
+ return rb_obj_dig(argc, argv, self, Qnil);
+}
+
+/*
* Arrays are ordered, integer-indexed collections of any object.
*
* Array indexing starts at 0, as in C or Java. A negative index is assumed
@@ -5882,6 +5905,7 @@ Init_Array(void)
rb_define_method(rb_cArray, "bsearch", rb_ary_bsearch, 0);
rb_define_method(rb_cArray, "bsearch_index", rb_ary_bsearch_index, 0);
rb_define_method(rb_cArray, "any?", rb_ary_any_p, 0);
+ rb_define_method(rb_cArray, "dig", rb_ary_dig, -1);
id_cmp = rb_intern("<=>");
id_random = rb_intern("random");
diff --git a/hash.c b/hash.c
index c179ff4ce1..ae65aa4a1e 100644
--- a/hash.c
+++ b/hash.c
@@ -2691,6 +2691,29 @@ rb_hash_any_p(VALUE hash)
return ret;
}
+/*
+ * call-seq:
+ * hsh.dig(key, ...) -> object
+ *
+ * Retrieves the value object corresponding to the each <i>key</i>
+ * objects repeatedly.
+ *
+ * h = { foo: {bar: {baz: 1}}}
+ *
+ * h.dig(:foo, :bar, :baz) #=> 1
+ * h.dig(:foo, :zot) #=> nil
+ */
+
+VALUE
+rb_hash_dig(int argc, VALUE *argv, VALUE self)
+{
+ rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
+ self = rb_hash_aref(self, *argv);
+ if (!--argc) return self;
+ ++argv;
+ return rb_obj_dig(argc, argv, self, Qnil);
+}
+
static int path_tainted = -1;
static char **origenviron;
@@ -4114,6 +4137,7 @@ Init_Hash(void)
rb_define_method(rb_cHash,"compare_by_identity?", rb_hash_compare_by_id_p, 0);
rb_define_method(rb_cHash, "any?", rb_hash_any_p, 0);
+ rb_define_method(rb_cHash, "dig", rb_hash_dig, -1);
/* Document-class: ENV
*
diff --git a/internal.h b/internal.h
index eb78a88ae1..f7e4200f6c 100644
--- a/internal.h
+++ b/internal.h
@@ -653,6 +653,7 @@ VALUE rb_ary_last(int, const VALUE *, VALUE);
void rb_ary_set_len(VALUE, long);
void rb_ary_delete_same(VALUE, VALUE);
VALUE rb_ary_tmp_new_fill(long capa);
+VALUE rb_ary_at(VALUE, VALUE);
size_t rb_ary_memsize(VALUE);
#ifdef __GNUC__
#define rb_ary_new_from_args(n, ...) \
@@ -968,6 +969,7 @@ void rb_obj_copy_ivar(VALUE dest, VALUE obj);
VALUE rb_obj_equal(VALUE obj1, VALUE obj2);
VALUE rb_class_search_ancestor(VALUE klass, VALUE super);
double rb_num_to_dbl(VALUE val);
+VALUE rb_obj_dig(int argc, VALUE *argv, VALUE self, VALUE notfound);
struct RBasicRaw {
VALUE flags;
diff --git a/object.c b/object.c
index 9796ea937d..50c2fe04f5 100644
--- a/object.c
+++ b/object.c
@@ -3144,6 +3144,52 @@ rb_f_hash(VALUE obj, VALUE arg)
return rb_Hash(arg);
}
+struct dig_method {
+ VALUE klass;
+ int basic;
+};
+
+static ID id_dig;
+
+static int
+dig_basic_p(VALUE obj, struct dig_method *cache)
+{
+ VALUE klass = RBASIC_CLASS(obj);
+ if (klass != cache->klass) {
+ cache->klass = klass;
+ cache->basic = rb_method_basic_definition_p(klass, id_dig);
+ }
+ return cache->basic;
+}
+
+VALUE
+rb_obj_dig(int argc, VALUE *argv, VALUE obj, VALUE notfound)
+{
+ struct dig_method hash = {Qnil}, ary = {Qnil};
+
+ for (; argc > 0; ++argv, --argc) {
+ if (!SPECIAL_CONST_P(obj)) {
+ switch (BUILTIN_TYPE(obj)) {
+ case T_HASH:
+ if (dig_basic_p(obj, &hash)) {
+ obj = rb_hash_aref(obj, *argv);
+ continue;
+ }
+ break;
+ case T_ARRAY:
+ if (dig_basic_p(obj, &ary)) {
+ obj = rb_ary_at(obj, *argv);
+ continue;
+ }
+ }
+ }
+ obj = rb_check_funcall(obj, id_dig, argc, argv);
+ if (obj == Qundef) return notfound;
+ break;
+ }
+ return obj;
+}
+
/*
* Document-class: Class
*
@@ -3521,5 +3567,6 @@ void
Init_Object(void)
{
id_to_f = rb_intern_const("to_f");
+ id_dig = rb_intern_const("dig");
InitVM(Object);
}
diff --git a/test/ruby/test_array.rb b/test/ruby/test_array.rb
index 74da8c1dab..0922cb4c65 100644
--- a/test/ruby/test_array.rb
+++ b/test/ruby/test_array.rb
@@ -2651,6 +2651,12 @@ class TestArray < Test::Unit::TestCase
end
end
+ def test_dig
+ h = @cls[@cls[{a: 1}], 0]
+ assert_equal(1, h.dig(0, 0, :a))
+ assert_nil(h.dig(1, 0))
+ end
+
private
def need_continuation
unless respond_to?(:callcc, true)
diff --git a/test/ruby/test_hash.rb b/test/ruby/test_hash.rb
index e8f0f87326..ee51c8ed7d 100644
--- a/test/ruby/test_hash.rb
+++ b/test/ruby/test_hash.rb
@@ -1302,6 +1302,12 @@ class TestHash < Test::Unit::TestCase
x = x
end
+ def test_dig
+ h = @cls[a: @cls[b: [1, 2, 3]], c: 4]
+ assert_equal(1, h.dig(:a, :b, 0))
+ assert_nil(h.dig(:c, 1))
+ end
+
class TestSubHash < TestHash
class SubHash < Hash
def reject(*)