diff --git a/internal.h b/internal.h index 71ded35948..7aefcda8e8 100644 --- a/internal.h +++ b/internal.h @@ -1394,6 +1394,7 @@ VALUE rb_rational_cmp(VALUE self, VALUE other); VALUE rb_reg_compile(VALUE str, int options, const char *sourcefile, int sourceline); VALUE rb_reg_check_preprocess(VALUE); long rb_reg_search0(VALUE, VALUE, long, int, int); +VALUE rb_reg_match_p(VALUE re, VALUE str, long pos); void rb_backref_set_string(VALUE string, long pos, long len); int rb_match_count(VALUE match); int rb_match_nth_defined(int nth, VALUE match); diff --git a/re.c b/re.c index 95ba903480..17893dd8e8 100644 --- a/re.c +++ b/re.c @@ -3225,19 +3225,22 @@ rb_reg_match_m(int argc, VALUE *argv, VALUE re) static VALUE rb_reg_match_m_p(int argc, VALUE *argv, VALUE re) { - VALUE str, initpos; - long pos = 0; + long pos = rb_check_arity(argc, 1, 2) > 1 ? NUM2LONG(argv[1]) : 0; + return rb_reg_match_p(re, argv[0], pos); +} + +VALUE +rb_reg_match_p(VALUE re, VALUE str, long pos) +{ regex_t *reg; onig_errmsg_buffer err = ""; OnigPosition result; const UChar *start, *end; int tmpreg; - rb_scan_args(argc, argv, "11", &str, &initpos); if (NIL_P(str)) return Qfalse; - str = SYMBOL_P(str) ? rb_sym2str(str) : rb_str_to_str(str); - if (argc == 2) { - pos = NUM2LONG(initpos); + str = SYMBOL_P(str) ? rb_sym2str(str) : StringValue(str); + if (pos) { if (pos < 0) { pos += NUM2LONG(rb_str_length(str)); if (pos < 0) return Qfalse; diff --git a/string.c b/string.c index 9b96843f54..97c3e2527b 100644 --- a/string.c +++ b/string.c @@ -3601,6 +3601,32 @@ rb_str_match_m(int argc, VALUE *argv, VALUE str) return result; } +/* + * call-seq: + * str.match?(pattern) -> true or false + * str.match?(pattern, pos) -> true or false + * + * Converts _pattern_ to a +Regexp+ (if it isn't already one), then + * returns a +true+ or +false+ indicates whether the regexp is + * matched _str_ or not without updating $~ and other + * related variables. If the second parameter is present, it + * specifies the position in the string to begin the search. + * + * "Ruby".match?(/R.../) #=> true + * "Ruby".match?(/R.../, 1) #=> false + * "Ruby".match?(/P.../) #=> false + * $& #=> nil + */ + +static VALUE +rb_str_match_m_p(int argc, VALUE *argv, VALUE str) +{ + VALUE re; + rb_check_arity(argc, 1, 2); + re = get_pat(argv[0]); + return rb_reg_match_p(re, str, argc > 1 ? NUM2LONG(argv[1]) : 0); +} + enum neighbor_char { NEIGHBOR_NOT_CHAR, NEIGHBOR_FOUND, @@ -9738,6 +9764,19 @@ sym_match_m(int argc, VALUE *argv, VALUE sym) return rb_str_match_m(argc, argv, rb_sym2str(sym)); } +/* + * call-seq: + * sym.match?(obj) -> true or false + * + * Returns sym.to_s.match?(obj). + */ + +static VALUE +sym_match_m_p(int argc, VALUE *argv, VALUE sym) +{ + return rb_str_match_m_p(argc, argv, sym); +} + /* * call-seq: * sym[idx] -> char @@ -9924,6 +9963,7 @@ Init_String(void) rb_define_method(rb_cString, "empty?", rb_str_empty, 0); rb_define_method(rb_cString, "=~", rb_str_match, 1); rb_define_method(rb_cString, "match", rb_str_match_m, -1); + rb_define_method(rb_cString, "match?", rb_str_match_m_p, -1); rb_define_method(rb_cString, "succ", rb_str_succ, 0); rb_define_method(rb_cString, "succ!", rb_str_succ_bang, 0); rb_define_method(rb_cString, "next", rb_str_succ, 0); @@ -10070,6 +10110,7 @@ Init_String(void) rb_define_method(rb_cSymbol, "size", sym_length, 0); rb_define_method(rb_cSymbol, "empty?", sym_empty, 0); rb_define_method(rb_cSymbol, "match", sym_match_m, -1); + rb_define_method(rb_cSymbol, "match?", sym_match_m_p, -1); rb_define_method(rb_cSymbol, "upcase", sym_upcase, -1); rb_define_method(rb_cSymbol, "downcase", sym_downcase, -1); diff --git a/test/ruby/test_string.rb b/test/ruby/test_string.rb index 2bd35b1703..39477be220 100644 --- a/test/ruby/test_string.rb +++ b/test/ruby/test_string.rb @@ -2111,6 +2111,50 @@ CODE assert_raise(ArgumentError) { "foo".match } end + def test_match_p_regexp + /backref/ =~ 'backref' + # must match here, but not in a separate method, e.g., assert_send, + # to check if $~ is affected or not. + assert_equal(true, "".match?(//)) + assert_equal(true, :abc.match?(/.../)) + assert_equal(true, 'abc'.match?(/b/)) + assert_equal(true, 'abc'.match?(/b/, 1)) + assert_equal(true, 'abc'.match?(/../, 1)) + assert_equal(true, 'abc'.match?(/../, -2)) + assert_equal(false, 'abc'.match?(/../, -4)) + assert_equal(false, 'abc'.match?(/../, 4)) + assert_equal(true, "\u3042xx".match?(/../, 1)) + assert_equal(false, "\u3042x".match?(/../, 1)) + assert_equal(true, ''.match?(/\z/)) + assert_equal(true, 'abc'.match?(/\z/)) + assert_equal(true, 'Ruby'.match?(/R.../)) + assert_equal(false, 'Ruby'.match?(/R.../, 1)) + assert_equal(false, 'Ruby'.match?(/P.../)) + assert_equal('backref', $&) + end + + def test_match_p_string + /backref/ =~ 'backref' + # must match here, but not in a separate method, e.g., assert_send, + # to check if $~ is affected or not. + assert_equal(true, "".match?('')) + assert_equal(true, :abc.match?('...')) + assert_equal(true, 'abc'.match?('b')) + assert_equal(true, 'abc'.match?('b', 1)) + assert_equal(true, 'abc'.match?('..', 1)) + assert_equal(true, 'abc'.match?('..', -2)) + assert_equal(false, 'abc'.match?('..', -4)) + assert_equal(false, 'abc'.match?('..', 4)) + assert_equal(true, "\u3042xx".match?('..', 1)) + assert_equal(false, "\u3042x".match?('..', 1)) + assert_equal(true, ''.match?('\z')) + assert_equal(true, 'abc'.match?('\z')) + assert_equal(true, 'Ruby'.match?('R...')) + assert_equal(false, 'Ruby'.match?('R...', 1)) + assert_equal(false, 'Ruby'.match?('P...')) + assert_equal('backref', $&) + end + def test_clear s = "foo" * 100 s.clear