Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 80 additions & 16 deletions array.c
Original file line number Diff line number Diff line change
Expand Up @@ -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+.
*
Expand All @@ -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].
Expand All @@ -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) {
Expand All @@ -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<RARRAY_LEN(ary); i++) {
if (pos < 0)
pos += RARRAY_LEN(ary);
if (pos < 0)
return Qnil;
for (i=pos; i<RARRAY_LEN(ary); i++) {
VALUE e = RARRAY_AREF(ary, i);
if (rb_equal(e, val)) {
return LONG2NUM(i);
Expand All @@ -2229,9 +2257,9 @@ rb_ary_index(int argc, VALUE *argv, VALUE ary)

/*
* call-seq:
* rindex(object) -> 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 <tt>object == element</tt>.
*
Expand All @@ -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].
Expand All @@ -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);
Expand Down
9 changes: 6 additions & 3 deletions prism/constant_pool.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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.
Expand Down
15 changes: 13 additions & 2 deletions prism/prism.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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
Expand All @@ -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) {
Expand Down
15 changes: 15 additions & 0 deletions test/ruby/test_array.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down