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
81 changes: 7 additions & 74 deletions mrbgems/mruby-string-ext/src/string.c
Original file line number Diff line number Diff line change
Expand Up @@ -1993,67 +1993,6 @@ str_center_core(mrb_state *mrb, mrb_value self)
return mrb_str_cat_str(mrb, result, right_padding);
}

#ifdef MRB_UTF8_STRING
/*
* Given a character index, find the byte offset in a UTF-8 string.
* Returns -1 if the character index is out of bounds.
*/
static mrb_int
str_char_to_byte_offset(mrb_value str, mrb_int char_index)
{
struct RString *s = mrb_str_ptr(str);
const char *p = RSTR_PTR(s);
mrb_int byte_len = RSTR_LEN(s);

if (RSTR_SINGLE_BYTE_P(s) || RSTR_BINARY_P(s)) {
return char_index;
}

if (char_index < 0) return -1;

mrb_int byte_offset = 0;
mrb_int current_char_index = 0;
while (byte_offset < byte_len && current_char_index < char_index) {
mrb_int char_len = mrb_utf8len(p + byte_offset, p + byte_len - byte_offset);
if (char_len == 0) break;
byte_offset += char_len;
current_char_index++;
}

if (current_char_index < char_index) return -1;
return byte_offset;
}

/*
* Given a starting character index and a character length, find the byte length.
*/
static mrb_int
str_chars_to_byte_len(mrb_value str, mrb_int char_start, mrb_int char_len)
{
struct RString *s = mrb_str_ptr(str);
const char *p = RSTR_PTR(s);
mrb_int str_byte_len = RSTR_LEN(s);

if (RSTR_SINGLE_BYTE_P(s) || RSTR_BINARY_P(s)) {
return char_len;
}

mrb_int start_byte_offset = str_char_to_byte_offset(str, char_start);
if (start_byte_offset == -1) return 0;

mrb_int byte_offset = start_byte_offset;
mrb_int current_char_len = 0;
while (byte_offset < str_byte_len && current_char_len < char_len) {
mrb_int cl = mrb_utf8len(p + byte_offset, p + str_byte_len - byte_offset);
if (cl == 0) break;
byte_offset += cl;
current_char_len++;
}

return byte_offset - start_byte_offset;
}
#endif

static mrb_value
mrb_str_slice_bang(mrb_state *mrb, mrb_value self)
{
Expand All @@ -2077,13 +2016,12 @@ mrb_str_slice_bang(mrb_state *mrb, mrb_value self)
if (mrb_string_p(arg1)) {
mrb_int pos = mrb_str_index(mrb, self, RSTRING_PTR(arg1), RSTRING_LEN(arg1), 0);
if (pos == -1) return mrb_nil_value();
#ifdef MRB_UTF8_STRING
beg = str_char_count(mrb_str_substr(mrb, self, 0, pos));
/* The search runs over bytes, so a match may start inside a character;
mrb_str_byte_to_char() answers -1 there, and a match no character
index reaches is no match. */
beg = mrb_str_byte_to_char(mrb, self, pos);
if (beg < 0) return mrb_nil_value();
len = str_char_count(arg1);
#else
beg = pos;
len = RSTRING_LEN(arg1);
#endif
}
else if (mrb_range_p(arg1)) {
if (mrb_range_beg_len(mrb, arg1, &beg, &len, str_len, TRUE) != MRB_RANGE_OK) {
Expand Down Expand Up @@ -2111,13 +2049,8 @@ mrb_str_slice_bang(mrb_state *mrb, mrb_value self)
}
if (len < 0) len = 0;

#ifdef MRB_UTF8_STRING
mrb_int byte_beg = str_char_to_byte_offset(self, beg);
mrb_int byte_len = str_chars_to_byte_len(self, beg, len);
#else
mrb_int byte_beg = beg;
mrb_int byte_len = len;
#endif
mrb_int byte_beg = mrb_str_char_to_byte(mrb, self, 0, beg);
mrb_int byte_len = mrb_str_char_to_byte(mrb, self, byte_beg, len);

if (byte_beg < 0 || byte_beg > RSTRING_LEN(self) || byte_beg + byte_len > RSTRING_LEN(self)) {
return mrb_nil_value();
Expand Down
46 changes: 46 additions & 0 deletions mrbgems/mruby-string-ext/test/string.rb
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,52 @@ def assert_upto(exp, receiver, *args)
assert_raise(ArgumentError) { "foo".slice! }
end

assert('String#slice! with multibyte characters') do
a = "あいうえお"
assert_equal "えお", a.slice!(3, 2)
assert_equal "あいう", a

a = "あいう"
assert_equal "いう", a.slice!(1..2)
assert_equal "あ", a

a = "あいう"
assert_equal "う", a.slice!(-1)
assert_equal "あい", a

a = "aあいb"
assert_equal "あい", a.slice!(1, 2)
assert_equal "ab", a

a = "あい"
assert_equal "あい", a.slice!(0, 2)
assert_equal "", a

a = "あい"
assert_equal "", a.slice!(2, 1)
assert_equal "あい", a
end if UTF8STRING

assert('String#slice! with a multibyte match') do
a = "あいう"
assert_equal "い", a.slice!("い")
assert_equal "あう", a

a = "あいう"
assert_equal "う", a.slice!("う")
assert_equal "あい", a

a = "あいう"
assert_nil a.slice!("え")
assert_equal "あいう", a

# the search runs over bytes, so a match starting inside a character is
# not a match
a = "あ"
assert_nil a.slice!("\x81\x82")
assert_equal "あ", a
end if UTF8STRING

assert('String#succ') do
assert_equal "", "".succ
assert_equal "1", "0".succ
Expand Down
Loading