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
2 changes: 1 addition & 1 deletion .github/workflows/zjit-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ jobs:
rustup install ${{ matrix.rust_version }} --profile minimal
rustup default ${{ matrix.rust_version }}

- uses: taiki-e/install-action@06203676c62f0d3c765be3f2fcfbebbcb02d09f5 # v2.69.6
- uses: taiki-e/install-action@0d865d5cc6d507df4765f1f866bfae8bab4e2a73 # v2.69.7
with:
tool: nextest@0.9
if: ${{ matrix.test_task == 'zjit-check' }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/zjit-ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ jobs:
ruby-version: '3.1'
bundler: none

- uses: taiki-e/install-action@06203676c62f0d3c765be3f2fcfbebbcb02d09f5 # v2.69.6
- uses: taiki-e/install-action@0d865d5cc6d507df4765f1f866bfae8bab4e2a73 # v2.69.7
with:
tool: nextest@0.9
if: ${{ matrix.test_task == 'zjit-check' }}
Expand Down
2 changes: 1 addition & 1 deletion array.c
Original file line number Diff line number Diff line change
Expand Up @@ -6540,7 +6540,7 @@ rb_ary_uniq(VALUE ary)
* see also {Methods for Deleting}[rdoc-ref:Array@Methods+for+Deleting].
*/

static VALUE
VALUE
rb_ary_compact_bang(VALUE ary)
{
VALUE *p, *t, *end;
Expand Down
1 change: 1 addition & 0 deletions depend
Original file line number Diff line number Diff line change
Expand Up @@ -14354,6 +14354,7 @@ re.$(OBJEXT): $(hdrdir)/ruby.h
re.$(OBJEXT): $(hdrdir)/ruby/ruby.h
re.$(OBJEXT): $(top_srcdir)/internal/array.h
re.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
re.$(OBJEXT): $(top_srcdir)/internal/bignum.h
re.$(OBJEXT): $(top_srcdir)/internal/bits.h
re.$(OBJEXT): $(top_srcdir)/internal/box.h
re.$(OBJEXT): $(top_srcdir)/internal/class.h
Expand Down
1 change: 1 addition & 0 deletions internal/array.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ size_t rb_ary_size_as_embedded(VALUE ary);
void rb_ary_make_embedded(VALUE ary);
bool rb_ary_embeddable_p(VALUE ary);
VALUE rb_ary_diff(VALUE ary1, VALUE ary2);
VALUE rb_ary_compact_bang(VALUE ary);
RUBY_EXTERN VALUE rb_cArray_empty_frozen;

static inline VALUE rb_ary_entry_internal(VALUE ary, long offset);
Expand Down
78 changes: 75 additions & 3 deletions re.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "encindex.h"
#include "hrtime.h"
#include "internal.h"
#include "internal/bignum.h"
#include "internal/encoding.h"
#include "internal/error.h"
#include "internal/hash.h"
Expand Down Expand Up @@ -1187,7 +1188,7 @@ match_size(VALUE match)
return INT2FIX(RMATCH_REGS(match)->num_regs);
}

static int name_to_backref_number(struct re_registers *, VALUE, const char*, const char*);
static int name_to_backref_number(const struct re_registers *, VALUE, const char*, const char*);
NORETURN(static void name_to_backref_error(VALUE name));

static void
Expand Down Expand Up @@ -2147,7 +2148,7 @@ match_captures(VALUE match)
}

static int
name_to_backref_number(struct re_registers *regs, VALUE regexp, const char* name, const char* name_end)
name_to_backref_number(const struct re_registers *regs, VALUE regexp, const char* name, const char* name_end)
{
if (NIL_P(regexp)) return -1;
return onig_name_to_backref_number(RREGEXP_PTR(regexp),
Expand All @@ -2160,7 +2161,7 @@ name_to_backref_number(struct re_registers *regs, VALUE regexp, const char* name
name_to_backref_number((regs), (re), (name_ptr), (name_end)))

static int
namev_to_backref_number(struct re_registers *regs, VALUE re, VALUE name)
namev_to_backref_number(const struct re_registers *regs, VALUE re, VALUE name)
{
int num;

Expand Down Expand Up @@ -3629,6 +3630,76 @@ match_equal(VALUE match1, VALUE match2)
return Qtrue;
}

/*
* call-seq:
* integer_at(index, base = 10) -> integer or nil
* integer_at(name, base = 10) -> integer or nil
*
* Converts the matched substring to integer and return the result.
* +$~.integer_at(N)+ is equivalent to +$N&.to_i+.
*
* m = /(\d+{4})(\d+{2})(\d+{2})/.match("20260308")
* # => #<MatchData "20260308" 1:"2026" 2:"03" 3:"08">
* m.integer_at(0) # => 20260308
* m.integer_at(1) # => 2026
* m.integer_at(2) # => 3
* m.integer_at(3) # => 8
*
* m = /(?<y>\d+{4})(?<m>\d+{2})(?<d>\d+{2})/.match("20260308")
* m.integer_at("y") # => 2026
* m.integer_at("m") # => 3
* m.integer_at("d") # => 8
*
* If the substring does not match, returns +nil+.
*
* re = /(\d+)?/
* re.match("123").integer_at(1) #=> 123
* re.match("abc").integer_at(1) #=> nil
*
* The string is converted in decimal by default.
*
* /\d+/.match("011").integer_at(0) #=> 10
* /\d+/.match("011").integer_at(0, 12) #=> 13
* /\d+/.match("011").integer_at(0, 0) #=> 9
*
* See also MatchData#[], String#to_i.
*/
static VALUE
match_integer_at(int argc, VALUE *argv, VALUE match)
{
const struct re_registers *regs = RMATCH_REGS(match_check(match));

int base = 10;
VALUE idx;
long nth;

argc = rb_check_arity(argc, 1, 2);
if (FIXNUM_P(idx = argv[0])) {
nth = NUM2INT(idx);
}
else if ((nth = namev_to_backref_number(regs, RMATCH(match)->regexp, idx)) < 0) {
name_to_backref_error(idx);
}

if (argc > 1 && (base = NUM2INT(argv[1])) < 0) {
rb_raise(rb_eArgError, "invalid radix %d", base);
}

if (nth >= regs->num_regs) return Qnil;
if (nth < 0 && (nth += regs->num_regs) <= 0) return Qnil;

long start = BEG(nth), end = END(nth);
if (start < 0) return Qnil;
RUBY_ASSERT(start <= end, "%ld > %ld", start, end);

VALUE str = RMATCH(match)->str;
RUBY_ASSERT(end <= RSTRING_LEN(str), "%ld > %ld", end, RSTRING_LEN(str));

char *endp;
return rb_int_parse_cstr(RSTRING_PTR(str) + start, end - start, &endp, NULL,
base, RB_INT_PARSE_DEFAULT);
}

static VALUE
reg_operand(VALUE s, int check)
{
Expand Down Expand Up @@ -4908,4 +4979,5 @@ Init_Regexp(void)
rb_define_method(rb_cMatch, "hash", match_hash, 0);
rb_define_method(rb_cMatch, "eql?", match_equal, 1);
rb_define_method(rb_cMatch, "==", match_equal, 1);
rb_define_method(rb_cMatch, "integer_at", match_integer_at, -1);
}
86 changes: 33 additions & 53 deletions spec/ruby/core/data/deconstruct_keys_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,29 +34,6 @@
d.deconstruct_keys(['x', 'y']).should == {'x' => 1, 'y' => 2}
end

it "accepts argument position number as well but returns them as keys" do
klass = Data.define(:x, :y)
d = klass.new(1, 2)

d.deconstruct_keys([0, 1]).should == {0 => 1, 1 => 2}
d.deconstruct_keys([0] ).should == {0 => 1}
d.deconstruct_keys([-1] ).should == {-1 => 2}
end

it "ignores incorrect position numbers" do
klass = Data.define(:x, :y)
d = klass.new(1, 2)

d.deconstruct_keys([0, 3]).should == {0 => 1}
end

it "support mixing attribute names and argument position numbers" do
klass = Data.define(:x, :y)
d = klass.new(1, 2)

d.deconstruct_keys([0, :x]).should == {0 => 1, :x => 1}
end

it "returns an empty hash when there are more keys than attributes" do
klass = Data.define(:x, :y)
d = klass.new(1, 2)
Expand All @@ -72,50 +49,53 @@
d.deconstruct_keys([:x, :a]).should == {x: 1}
end

it "returns at first not existing argument position number" do
klass = Data.define(:x, :y)
d = klass.new(1, 2)

d.deconstruct_keys([3, 0]).should == {}
d.deconstruct_keys([0, 3]).should == {0 => 1}
end

it "accepts nil argument and return all the attributes" do
klass = Data.define(:x, :y)
d = klass.new(1, 2)

d.deconstruct_keys(nil).should == {x: 1, y: 2}
end

it "tries to convert a key with #to_int if index is not a String nor a Symbol, but responds to #to_int" do
klass = Data.define(:x, :y)
d = klass.new(1, 2)
ruby_bug "Bug #21844", ""..."4.1" do
it "tries to convert a key with #to_str if index is not a String nor a Symbol, but responds to #to_str" do
klass = Data.define(:x, :y)
d = klass.new(1, 2)

key = mock("to_int")
key.should_receive(:to_int).and_return(1)
key = mock("to_str")
key.should_receive(:to_str).and_return("y")

d.deconstruct_keys([key]).should == { key => 2 }
end
d.deconstruct_keys([key]).should == { "y" => 2 }
end

it "raises a TypeError if the conversion with #to_int does not return an Integer" do
klass = Data.define(:x, :y)
d = klass.new(1, 2)
it "raise an error on argument position number" do
klass = Data.define(:x, :y)
d = klass.new(1, 2)

key = mock("to_int")
key.should_receive(:to_int).and_return("not an Integer")
-> {
d.deconstruct_keys([0, 1])
}.should raise_error(TypeError, "0 is not a symbol nor a string")
end

-> {
d.deconstruct_keys([key])
}.should raise_consistent_error(TypeError, /can't convert MockObject into Integer/)
end
it "raises a TypeError if the conversion with #to_str does not return a String" do
klass = Data.define(:x, :y)
d = klass.new(1, 2)

it "raises TypeError if index is not a String, a Symbol and not convertible to Integer " do
klass = Data.define(:x, :y)
d = klass.new(1, 2)
key = mock("to_str")
key.should_receive(:to_str).and_return(0)

-> {
d.deconstruct_keys([0, []])
}.should raise_error(TypeError, "no implicit conversion of Array into Integer")
-> {
d.deconstruct_keys([key])
}.should raise_consistent_error(TypeError, /can't convert MockObject into String/)
end

it "raises TypeError if index is not a Symbol and not convertible to String " do
klass = Data.define(:x, :y)
d = klass.new(1, 2)

-> {
d.deconstruct_keys([0, []])
}.should raise_error(TypeError, "0 is not a symbol nor a string")
end
end

it "raise TypeError if passed anything except nil or array" do
Expand Down
38 changes: 38 additions & 0 deletions spec/ruby/core/matchdata/integer_at_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# -*- encoding: utf-8 -*-

require_relative '../../spec_helper'

ruby_version_is "4.1" do
describe "MatchData#integer_at" do
it "converts the corresponding match to an Integer and returns it when given an Integer" do
md = /(\d{4})(\d{2})(\d{2})/.match("20260308")
md.integer_at(0).should == 20260308
md.integer_at(1).should == 2026
md.integer_at(2).should == 3
end

it "returns nil on non-matching index matches" do
md = /\b(\d)?\b/.match("THX1138.")
md.integer_at(1).should == nil
end

it "returns nil on non-integer matches" do
md = /(\w)?/.match("THX1138.")
md.integer_at(1).should == nil
end

it "converts the match to an Integer in the given base" do
md = /\w+/.match("0c")
md.integer_at(0).should == 0
md.integer_at(0, 16).should == 12
end

it "converts the match to an Integer in the prefix when given base is zero" do
/\w+/.match("010").integer_at(0, 0).should == 010
/\w+/.match("0x10").integer_at(0, 0).should == 0x10
/\w+/.match("0d10").integer_at(0, 0).should == 0d10
/\w+/.match("0o10").integer_at(0, 0).should == 0o10
/\w+/.match("0b10").integer_at(0, 0).should == 0b10
end
end
end
Loading