diff --git a/spec/std/regex/match_data_spec.cr b/spec/std/regex/match_data_spec.cr index 1079dff145e1..7fc9c3219faa 100644 --- a/spec/std/regex/match_data_spec.cr +++ b/spec/std/regex/match_data_spec.cr @@ -65,12 +65,33 @@ describe "Regex::MatchData" do end it "with capture" do - matchdata(/f(o)o/, "foo").begin.should eq 0 - matchdata(/f(o)o/, "foo").begin(1).should eq 1 - matchdata(/f(o)o/, "foo").begin(-1).should eq 1 - matchdata(/f(o)o/, ".foo.").begin.should eq 1 - matchdata(/f(o)o/, ".foo.").begin(1).should eq 2 - matchdata(/f(o)o/, ".foo.").begin(-1).should eq 2 + md = matchdata(/f(o)o/, "foo") + md.begin.should eq 0 + md.begin(1).should eq 1 + md.begin(-1).should eq 1 + + md = matchdata(/f(o)o/, ".foo.") + md.begin.should eq 1 + md.begin(1).should eq 2 + md.begin(-1).should eq 2 + end + + it "with unmatched capture" do + md = matchdata(/f(x)?o/, "foo") + expect_raises(IndexError, "Capture group 1 was not matched") do + md.begin(1) + end + expect_raises(IndexError, "Capture group 1 was not matched") do + md.begin(-1) + end + + md = matchdata(/f(x)?o/, ".foo.") + expect_raises(IndexError, "Capture group 1 was not matched") do + md.begin(1) + end + expect_raises(IndexError, "Capture group 1 was not matched") do + md.begin(-1) + end end it "char index" do @@ -82,6 +103,24 @@ describe "Regex::MatchData" do it "char index" do matchdata(/foo/, "öfoo").byte_begin.should eq 2 end + + it "with unmatched capture" do + md = matchdata(/f(x)?o/, "foo") + expect_raises(IndexError, "Capture group 1 was not matched") do + md.byte_begin(1) + end + expect_raises(IndexError, "Capture group 1 was not matched") do + md.byte_begin(-1) + end + + md = matchdata(/f(x)?o/, ".foo.") + expect_raises(IndexError, "Capture group 1 was not matched") do + md.byte_begin(1) + end + expect_raises(IndexError, "Capture group 1 was not matched") do + md.byte_begin(-1) + end + end end describe "#end" do @@ -99,12 +138,33 @@ describe "Regex::MatchData" do end it "with capture" do - matchdata(/f(o)o/, "foo").end.should eq 3 - matchdata(/f(o)o/, "foo").end(1).should eq 2 - matchdata(/f(o)o/, "foo").end(-1).should eq 2 - matchdata(/f(o)o/, ".foo.").end.should eq 4 - matchdata(/f(o)o/, ".foo.").end(1).should eq 3 - matchdata(/f(o)o/, ".foo.").end(-1).should eq 3 + md = matchdata(/f(o)o/, "foo") + md.end.should eq 3 + md.end(1).should eq 2 + md.end(-1).should eq 2 + + md = matchdata(/f(o)o/, ".foo.") + md.end.should eq 4 + md.end(1).should eq 3 + md.end(-1).should eq 3 + end + + it "with unmatched capture" do + md = matchdata(/f(x)?o/, "foo") + expect_raises(IndexError, "Capture group 1 was not matched") do + md.end(1) + end + expect_raises(IndexError, "Capture group 1 was not matched") do + md.end(-1) + end + + md = matchdata(/f(x)?o/, ".foo.") + expect_raises(IndexError, "Capture group 1 was not matched") do + md.end(1) + end + expect_raises(IndexError, "Capture group 1 was not matched") do + md.end(-1) + end end it "char index" do @@ -116,6 +176,24 @@ describe "Regex::MatchData" do it "char index" do matchdata(/foo/, "öfoo").byte_end.should eq 5 end + + it "with unmatched capture" do + md = matchdata(/f(x)?o/, "foo") + expect_raises(IndexError, "Capture group 1 was not matched") do + md.byte_end(1) + end + expect_raises(IndexError, "Capture group 1 was not matched") do + md.byte_end(-1) + end + + md = matchdata(/f(x)?o/, ".foo.") + expect_raises(IndexError, "Capture group 1 was not matched") do + md.byte_end(1) + end + expect_raises(IndexError, "Capture group 1 was not matched") do + md.byte_end(-1) + end + end end describe "#[]" do diff --git a/src/regex/match_data.cr b/src/regex/match_data.cr index 89e12204986e..085c829073e8 100644 --- a/src/regex/match_data.cr +++ b/src/regex/match_data.cr @@ -59,10 +59,15 @@ class Regex # When *n* is `0` or not given, uses the match of the entire `Regex`. # Otherwise, uses the match of the *n*th capture group. # + # Raises `IndexError` if the index is out of range or the respective + # subpattern is unused. + # # ``` # "Crystal".match(/r/).not_nil!.begin(0) # => 1 # "Crystal".match(/r(ys)/).not_nil!.begin(1) # => 2 # "クリスタル".match(/リ(ス)/).not_nil!.begin(0) # => 1 + # "Crystal".match(/r/).not_nil!.begin(1) # IndexError: Invalid capture group index: 1 + # "Crystal".match(/r(x)?/).not_nil!.begin(1) # IndexError: Capture group 1 was not matched # ``` def begin(n = 0) : Int32 @string.byte_index_to_char_index(byte_begin(n)).not_nil! @@ -73,10 +78,15 @@ class Regex # When *n* is `0` or not given, uses the match of the entire `Regex`. # Otherwise, uses the match of the *n*th capture group. # + # Raises `IndexError` if the index is out of range or the respective + # subpattern is unused. + # # ``` # "Crystal".match(/r/).not_nil!.end(0) # => 2 # "Crystal".match(/r(ys)/).not_nil!.end(1) # => 4 # "クリスタル".match(/リ(ス)/).not_nil!.end(0) # => 3 + # "Crystal".match(/r/).not_nil!.end(1) # IndexError: Invalid capture group index: 1 + # "Crystal".match(/r(x)?/).not_nil!.end(1) # IndexError: Capture group 1 was not matched # ``` def end(n = 0) : Int32 @string.byte_index_to_char_index(byte_end(n)).not_nil! @@ -87,15 +97,22 @@ class Regex # When *n* is `0` or not given, uses the match of the entire `Regex`. # Otherwise, uses the match of the *n*th capture group. # + # Raises `IndexError` if the index is out of range or the respective + # subpattern is unused. + # # ``` # "Crystal".match(/r/).not_nil!.byte_begin(0) # => 1 # "Crystal".match(/r(ys)/).not_nil!.byte_begin(1) # => 2 # "クリスタル".match(/リ(ス)/).not_nil!.byte_begin(0) # => 3 + # "Crystal".match(/r/).not_nil!.byte_begin(1) # IndexError: Invalid capture group index: 1 + # "Crystal".match(/r(x)?/).not_nil!.byte_begin(1) # IndexError: Capture group 1 was not matched # ``` def byte_begin(n = 0) : Int32 check_index_out_of_bounds n n += size if n < 0 - @ovector[n * 2] + value = @ovector[n * 2] + raise_capture_group_was_not_matched(n) if value < 0 + value end # Returns the position of the next byte after the match. @@ -103,15 +120,22 @@ class Regex # When *n* is `0` or not given, uses the match of the entire `Regex`. # Otherwise, uses the match of the *n*th capture group. # + # Raises `IndexError` if the index is out of range or the respective + # subpattern is unused. + # # ``` # "Crystal".match(/r/).not_nil!.byte_end(0) # => 2 # "Crystal".match(/r(ys)/).not_nil!.byte_end(1) # => 4 # "クリスタル".match(/リ(ス)/).not_nil!.byte_end(0) # => 9 + # "Crystal".match(/r/).not_nil!.byte_end(1) # IndexError: Invalid capture group index: 1 + # "Crystal".match(/r(x)?/).not_nil!.byte_end(1) # IndexError: Capture group 1 was not matched # ``` def byte_end(n = 0) : Int32 check_index_out_of_bounds n n += size if n < 0 - @ovector[n * 2 + 1] + value = @ovector[n * 2 + 1] + raise_capture_group_was_not_matched(n) if value < 0 + value end # Returns the match of the *n*th capture group, or `nil` if there isn't