diff --git a/array.c b/array.c index 4b1343b6fa51bc..5af5c856a8dc21 100644 --- a/array.c +++ b/array.c @@ -2167,12 +2167,12 @@ rb_ary_rfind(int argc, VALUE *argv, VALUE ary) /* * call-seq: - * find_index(object) -> integer or nil + * find_index(object, offset: 0) -> integer or nil * find_index {|element| ... } -> integer or nil - * find_index -> new_enumerator - * index(object) -> integer or nil + * find_index(offset: 0) -> new_enumerator + * index(object, offset: 0) -> integer or nil * index {|element| ... } -> integer or nil - * index -> new_enumerator + * index(offset: 0) -> new_enumerator * * Returns the zero-based integer index of a specified element, or +nil+. * @@ -2194,6 +2194,20 @@ rb_ary_rfind(int argc, VALUE *argv, VALUE ary) * * Returns +nil+ if the block never returns a truthy value. * + * When +offset+ is non-negative, begins the search at position +offset+; + * the returned index is relative to the beginning of +self+: + * + * a = [:foo, 'bar', 2, 'bar'] + * a.index('bar', offset: 1) # => 1 + * a.index('bar', offset: 2) # => 3 + * a.index('bar', offset: 4) # => nil + * + * With negative integer argument +offset+, selects the search position by counting backward + * from the end of +self+: + * + * a = [:foo, 'bar', 2, 'bar'] + * a.index('bar', offset: -2) # => 3 + * * With neither an argument nor a block given, returns a new Enumerator. * * Related: see {Methods for Querying}[rdoc-ref:Array@Methods+for+Querying]. @@ -2202,7 +2216,8 @@ rb_ary_rfind(int argc, VALUE *argv, VALUE ary) static VALUE rb_ary_index(int argc, VALUE *argv, VALUE ary) { - VALUE val; + VALUE val, opts = Qnil, initpos = Qnil; + long pos = 0; long i; if (argc == 0) { @@ -2214,11 +2229,24 @@ rb_ary_index(int argc, VALUE *argv, VALUE ary) } return Qnil; } - rb_check_arity(argc, 0, 1); - val = argv[0]; + rb_check_arity(argc, 0, 2); + rb_scan_args(argc, argv, "1:", &val, &opts); + if (!NIL_P(opts)) { + static ID keywords[1]; + if (!keywords[0]) { + keywords[0] = rb_intern_const("offset"); + } + rb_get_kwargs(opts, keywords, 0, 1, &initpos); + if (!UNDEF_P(initpos)) + pos = NUM2LONG(initpos); + } if (rb_block_given_p()) rb_warn("given block not used"); - for (i=0; i integer or nil + * rindex(object, offset: nil) -> integer or nil * rindex {|element| ... } -> integer or nil - * rindex -> new_enumerator + * rindex(offset: nil) -> new_enumerator * * Returns the index of the last element for which object == element. * @@ -2250,6 +2278,23 @@ rb_ary_index(int argc, VALUE *argv, VALUE ary) * * Returns +nil+ if the block never returns a truthy value. * + * When +offset+ is non-negative, it specifies the maximum starting position in the + * array to end the search: + * + * a = [:foo, 'bar', 2, 'bar'] + * a.rindex('bar', offset: 0) # => 3 + * a.rindex(2, offset: 1) # => 2 + * a.rindex(2, offset: 3) # => nil + * + * With negative integer argument +offset+, + * selects the search position by counting backward from the end of +self+: + * + * a = [:foo, 'bar', 2, 'bar'] + * a.rindex('bar', -1) # => 3 + * a.rindex('bar', -2) # => 1 + * a.rindex('bar', -3) # => 1 + * a.rindex('bar', -4) # => nil + * * When neither an argument nor a block is given, returns a new Enumerator. * * Related: see {Methods for Querying}[rdoc-ref:Array@Methods+for+Querying]. @@ -2258,25 +2303,44 @@ rb_ary_index(int argc, VALUE *argv, VALUE ary) static VALUE rb_ary_rindex(int argc, VALUE *argv, VALUE ary) { - VALUE val; - long i = RARRAY_LEN(ary), len; + VALUE val, opts = Qnil, offset_arg = Qnil; + long i = RARRAY_LEN(ary) - 1, len, end_pos = 0, offset; if (argc == 0) { RETURN_ENUMERATOR(ary, 0, 0); - while (i--) { + while (i >= 0) { if (RTEST(rb_yield(RARRAY_AREF(ary, i)))) return LONG2NUM(i); if (i > (len = RARRAY_LEN(ary))) { i = len; } + i--; } return Qnil; } - rb_check_arity(argc, 0, 1); - val = argv[0]; + rb_check_arity(argc, 0, 2); + rb_scan_args(argc, argv, "1:", &val, &opts); + if (!NIL_P(opts)) { + static ID keywords[1]; + if (!keywords[0]) { + keywords[0] = rb_intern_const("offset"); + } + rb_get_kwargs(opts, keywords, 0, 1, &offset_arg); + if (!UNDEF_P(offset_arg)){ + offset = NUM2LONG(offset_arg); + if (offset < 0) { + i += offset + 1; + } + else { + end_pos = offset; + } + } + } if (rb_block_given_p()) rb_warn("given block not used"); - while (i--) { + if (i < 0 || end_pos >= RARRAY_LEN(ary)) + return Qnil; + for (; i >= end_pos; i--) { VALUE e = RARRAY_AREF(ary, i); if (rb_equal(e, val)) { return LONG2NUM(i); diff --git a/prism/constant_pool.c b/prism/constant_pool.c index 90201ebb8e35fc..6e9c5c6ebaa73f 100644 --- a/prism/constant_pool.c +++ b/prism/constant_pool.c @@ -245,7 +245,9 @@ pm_constant_pool_find(const pm_constant_pool_t *pool, const uint8_t *start, size pm_constant_pool_bucket_t *bucket; while (bucket = &pool->buckets[index], bucket->id != PM_CONSTANT_ID_UNSET) { - if ((bucket->length == length) && memcmp(bucket->start, start, length) == 0) { + // Compare the stored hash before touching the contents so that probe + // collisions are rejected without a memcmp call. + if ((bucket->hash == hash) && (bucket->length == length) && memcmp(bucket->start, start, length) == 0) { return bucket->id; } @@ -274,8 +276,9 @@ pm_constant_pool_insert(pm_arena_t *arena, pm_constant_pool_t *pool, const uint8 while (bucket = &pool->buckets[index], bucket->id != PM_CONSTANT_ID_UNSET) { // If there is a collision, then we need to check if the content is the // same as the content we are trying to insert. If it is, then we can - // return the id of the existing constant. - if ((bucket->length == length) && memcmp(bucket->start, start, length) == 0) { + // return the id of the existing constant. Compare the stored hash + // first so that probe collisions are rejected without a memcmp call. + if ((bucket->hash == hash) && (bucket->length == length) && memcmp(bucket->start, start, length) == 0) { // Since we have found a match, we need to check if this is // attempting to insert a shared or an owned constant. We want to // prefer shared constants since they don't require allocations. diff --git a/prism/prism.c b/prism/prism.c index 9ec1d33fda7fbf..3b6b6f64fb9f15 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -8716,6 +8716,13 @@ lex_identifier(pm_parser_t *parser, bool previous_command_start) { if (encoding_changed) { return parser->encoding->isupper_char(current_start, end - current_start) ? PM_TOKEN_CONSTANT : PM_TOKEN_IDENTIFIER; } + + /* Identifiers usually start with an ASCII byte, for which the uppercase + * check is a simple range comparison. This avoids the call into the + * encoding module for every identifier. */ + if (*current_start < 0x80) { + return (*current_start >= 'A' && *current_start <= 'Z') ? PM_TOKEN_CONSTANT : PM_TOKEN_IDENTIFIER; + } return pm_encoding_utf_8_isupper_char(current_start, end - current_start) ? PM_TOKEN_CONSTANT : PM_TOKEN_IDENTIFIER; } @@ -10093,7 +10100,11 @@ parser_lex(pm_parser_t *parser) { // token. Skip runs of inline whitespace in bulk to avoid per-character // stores back to parser->current.end. bool chomping = true; - while (parser->current.end < parser->end && chomping) { + while (chomping) { + /* Skip the run of inline whitespace in bulk, then decide what + * to do based on the first byte after it. Handling both in a + * single pass avoids re-entering the scan when the run was + * non-empty, which is the common case. */ { static const uint8_t inline_whitespace[256] = { [' '] = 1, ['\t'] = 1, ['\f'] = 1, ['\v'] = 1 @@ -10103,8 +10114,8 @@ parser_lex(pm_parser_t *parser) { if (scan > parser->current.end) { parser->current.end = scan; space_seen = true; - continue; } + if (scan >= parser->end) break; } switch (*parser->current.end) { diff --git a/test/ruby/test_array.rb b/test/ruby/test_array.rb index 36f88016149e2a..5f47b652f5754f 100644 --- a/test/ruby/test_array.rb +++ b/test/ruby/test_array.rb @@ -1162,7 +1162,14 @@ def test_index assert_nil(a.index('ca')) assert_nil(a.index([1,2])) + assert_equal(1, a.index(99, offset: 0)) + assert_equal(3, a.index(99, offset: 2)) + assert_equal(2, a.index(/a/, offset: -4)) + assert_nil(a.index(99, offset: a.size)) + assert_nil(a.index(99, offset: -a.size - 1)) + assert_equal(1, assert_warn(/given block not used/) {a.index(99) {|x| x == 'cat' }}) + assert_equal(3, assert_warn(/given block not used/) {a.index(99, offset: 2) {|x| x == 'cat' }}) end def test_values_at @@ -1537,7 +1544,15 @@ def test_rindex assert_nil(a.rindex('ca')) assert_nil(a.rindex([1,2])) + assert_equal(3, a.rindex(99, offset: 0)) + assert_equal(3, a.rindex(99, offset: 2)) + assert_equal(1, a.rindex(99, offset: -3)) + assert_nil(a.rindex(/a/, offset: -4)) + assert_nil(a.index(99, offset: a.size)) + assert_nil(a.index(99, offset: -a.size - 1)) + assert_equal(3, assert_warning(/given block not used/) {a.rindex(99) {|x| x == [1,2,3] }}) + assert_equal(3, assert_warning(/given block not used/) {a.rindex(99, offset: 2) {|x| x == [1,2,3] }}) bug15951 = "[Bug #15951]" o2 = Object.new