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
198 changes: 198 additions & 0 deletions mrbgems/mruby-encoding/test/string.rb
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,201 @@
assert_equal Encoding::UTF_8, d.encoding
end
end

assert('what a string built out of a byte-read string claims') do
# A copy carries the byte reading with the bytes, and so does everything
# else here that builds a string out of them: the pieces cut out of the
# string, its repetitions and sums, and the strings its bytes are shoveled
# or spliced into, the pads included.
if UTF8STRING
b = "\xE3\x81\x82".b # the bytes of a three-byte character, read as bytes
assert_equal Encoding::BINARY, b.dup.encoding
assert_equal Encoding::BINARY, b.reverse.encoding
# a piece cut out of the string, and a repetition of it
assert_equal Encoding::BINARY, b[0].encoding
assert_true b[0].valid_encoding?
assert_equal Encoding::BINARY, b.chars[0].encoding
assert_equal Encoding::BINARY, (171.chr * 2).encoding
assert_true (171.chr * 2).valid_encoding?
# a sum with a byte-read operand on either side
assert_equal Encoding::BINARY, (171.chr + 171.chr).encoding
assert_true (171.chr + 171.chr).valid_encoding?
assert_equal Encoding::BINARY, ("abc" + 171.chr).encoding
# a string the bytes were shoveled or spliced into
s = ""
s << 171.chr
assert_equal Encoding::BINARY, s.encoding
assert_true s.valid_encoding?
assert_equal Encoding::BINARY, [171.chr, 171.chr].join.encoding
assert_equal Encoding::BINARY, "ab".gsub("a", 171.chr).encoding
# the receiver's bytes in wider clothes are read the way the receiver was
assert_equal Encoding::BINARY, 171.chr.ljust(3).encoding
assert_equal Encoding::BINARY, 171.chr.rjust(3).encoding
end
end

assert('a string cut out of a byte-read string') do
# A subrange of a byte-read string holds nothing but bytes of it, so it is
# read the same way. Every piece used to come back UTF-8, handing bytes that
# spell no character a claim they could not honor.
if UTF8STRING
b = "\xE3\x81\x82".b # the bytes of a three-byte character, read as bytes
each_char_pieces = []
b.each_char { |c| each_char_pieces << c }
[b[0], b[0, 2], b[1..-1], b.chars[1], each_char_pieces[2],
b.byteslice(0, 2), b.dup.slice!(0)].each do |piece|
assert_equal Encoding::BINARY, piece.encoding
assert_true piece.valid_encoding?
end
assert_equal [0xE3], b[0].bytes
assert_equal [0x81, 0x82], b[1..-1].bytes
# what Integer#chr hands back for a stray byte stays byte-read when cut
assert_equal Encoding::BINARY, 171.chr[0].encoding
# splitting on a byte-read separator cuts byte-read pieces, and so does
# splitting on line ends
assert_equal Encoding::BINARY, b.split(b[1])[0].encoding
assert_equal Encoding::BINARY, "a\nb".b.lines[0].encoding
# a piece of a string read as UTF-8 goes on reading as UTF-8
assert_equal Encoding::UTF_8, "あい"[1].encoding
assert_equal "い", "あい"[1]
end
end

assert('a string built by repeating a byte-read string') do
# `*` lays the same bytes down over again, so what it builds is read the
# way the receiver was.
if UTF8STRING
s = 171.chr * 2
assert_equal [171, 171], s.bytes
assert_equal Encoding::BINARY, s.encoding
assert_true s.valid_encoding?
assert_equal Encoding::BINARY, ("abc".b * 2).encoding
assert_equal Encoding::UTF_8, ("あ" * 2).encoding
end
end

assert('String#+ with a byte-read operand') do
if UTF8STRING
bin = 171.chr # a byte spelling no character, read as bytes
# a byte-read operand carrying a byte above ASCII hands the sum bytes no
# other reading holds, so its reading wins
[bin + bin, "abc" + bin, bin + "abc", "" + bin].each do |s|
assert_equal Encoding::BINARY, s.encoding
assert_true s.valid_encoding?
end
assert_equal [97, 98, 99, 171], ("abc" + bin).bytes
# CRuby refuses these pairs outright; here the sum says nothing rather
# than something false
assert_equal Encoding::BINARY, ("あ" + bin).encoding
assert_equal Encoding::BINARY, (bin + "あ").encoding
# a byte-read operand of ASCII bytes reads as the other operand as it
# stands, so it yields to it
assert_equal Encoding::UTF_8, ("abc".b + "あ").encoding
assert_equal Encoding::UTF_8, ("あ" + "abc".b).encoding
assert_true ("abc".b + "あ").valid_encoding?
# two byte-read operands stay byte-read even over ASCII bytes
assert_equal Encoding::BINARY, ("abc".b + "def".b).encoding
# and two UTF-8 operands are what they were
assert_equal Encoding::UTF_8, ("あ" + "い").encoding
end
end

assert('bytes shoveled or spliced into a string') do
# Bytes that were read as bytes and go above ASCII spell no character in
# the string they are appended or spliced into, so they hand it the byte
# reading along with themselves. ASCII bytes read the same under any
# reading and move nothing.
if UTF8STRING
s = ""
s << 171.chr
assert_equal Encoding::BINARY, s.encoding
assert_true s.valid_encoding?
assert_equal [171], s.bytes
# concat and interpolation reach the same append
t = "abc"
t.concat(171.chr)
assert_equal Encoding::BINARY, t.encoding
assert_equal Encoding::BINARY, "<#{171.chr}>".encoding
# so do join and the replacement gsub splices in
assert_equal Encoding::BINARY, [171.chr, 171.chr].join.encoding
assert_equal Encoding::BINARY, ["a", 171.chr].join("-").encoding
assert_equal Encoding::BINARY, "a\x80b".b.gsub("a", "-").encoding
assert_equal Encoding::BINARY, "ab".gsub("a", 171.chr).encoding
# ASCII bytes say nothing about the reading
u = ""
u << "abc".b
assert_equal Encoding::UTF_8, u.encoding
# an Integer is read as a code point, and a code point is a character
v = ""
v << 171
assert_equal Encoding::UTF_8, v.encoding
assert_equal [0xC2, 0xAB], v.bytes
# a byte-read receiver reads everything as bytes already; CRuby lifts one
# of ASCII bytes to the argument's reading, which is a claim this string
# never makes, so it stays as it is
w = "".b
w << "あ"
assert_equal Encoding::BINARY, w.encoding
assert_equal [0xE3, 0x81, 0x82], w.bytes
# a splice into the middle or the front is the same landing
x = "ab"
x.insert(1, 171.chr)
assert_equal Encoding::BINARY, x.encoding
assert_equal [97, 171, 98], x.bytes
y = "ab"
y.prepend(171.chr)
assert_equal Encoding::BINARY, y.encoding
z = "ab"
z[0, 1] = 171.chr
assert_equal Encoding::BINARY, z.encoding
assert_true z.valid_encoding?
# and a splice of ASCII bytes moves nothing, wherever it lands
za = "ab"
za.insert(1, "x".b)
za.prepend("y".b)
za[0, 1] = "z".b
assert_equal Encoding::UTF_8, za.encoding
end
end

assert('String#append_as_bytes leaves the reading alone') do
# append_as_bytes takes only the bytes of what it is given; the receiver's
# reading does not move, whatever lands in it. CRuby specifies exactly this.
if UTF8STRING
s = "あ"
s.append_as_bytes(171)
assert_equal Encoding::UTF_8, s.encoding
assert_false s.valid_encoding?
assert_equal [0xE3, 0x81, 0x82, 171], s.bytes
t = "あ"
t.append_as_bytes(171.chr)
assert_equal Encoding::UTF_8, t.encoding
assert_equal [0xE3, 0x81, 0x82, 171], t.bytes
end
end

assert('a byte-read string padded to width') do
# ljust, rjust and center hand back the receiver's bytes in wider clothes,
# so the result is read the way the receiver was, ASCII bytes and all. A
# pad read as bytes marks the result the way any appended byte-read bytes
# do.
if UTF8STRING
s = 171.chr
[s.ljust(3), s.rjust(3), s.center(4)].each do |padded|
assert_equal Encoding::BINARY, padded.encoding
assert_true padded.valid_encoding?
end
assert_equal [171, 32, 32], s.ljust(3).bytes
assert_equal [32, 32, 171], s.rjust(3).bytes
assert_equal [32, 171, 32, 32], s.center(4).bytes
# an ASCII receiver read as bytes keeps the byte reading too
assert_equal Encoding::BINARY, "abc".b.ljust(5).encoding
assert_equal Encoding::BINARY, "abc".b.rjust(5).encoding
assert_equal Encoding::BINARY, "abc".b.center(5).encoding
# a byte-read pad above ASCII marks a plain receiver's result
assert_equal Encoding::BINARY, "ab".center(4, 255.chr).encoding
# a byte-read pad of ASCII bytes moves nothing
assert_equal Encoding::UTF_8, "ab".center(4, "-".b).encoding
assert_equal Encoding::UTF_8, "あ".center(3).encoding
end
end
7 changes: 7 additions & 0 deletions mrbgems/mruby-regexp/mrbgem.rake
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ MRuby::Gem::Specification.new('mruby-regexp') do |spec|
spec.add_dependency 'mruby-enumerator', :core => 'mruby-enumerator'
end

# Same deal for what a piece of a match is read as: the marking a byte-read
# string carries is only visible through mruby-encoding, so mrbtest can only
# ask about it when that gem is part of the state.
if build.gems.any? {|g| g.name == 'mruby-encoding'}
spec.add_test_dependency 'mruby-encoding', :core => 'mruby-encoding'
end

# Same deal for `Symbol#[]` and `#slice`, which live in mruby-symbol-ext and
# delegate to the String methods: the regexp form is this gem's, so mrbtest
# can only exercise it when that gem is part of the state.
Expand Down
27 changes: 26 additions & 1 deletion mrbgems/mruby-regexp/src/regexp.c
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,10 @@ static mrb_value
re_byte_substr(mrb_state *mrb, mrb_value str, mrb_int beg, mrb_int len)
{
if (beg < 0 || len < 0 || beg + len > RSTRING_LEN(str)) return mrb_nil_value();
return mrb_str_new(mrb, RSTRING_PTR(str) + beg, len);
mrb_value ret = mrb_str_new(mrb, RSTRING_PTR(str) + beg, len);
/* a piece of a byte-read subject is bytes of it, read the same way */
RSTR_COPY_BINARY_FLAG(mrb_str_ptr(ret), mrb_str_ptr(str));
return ret;
}

/* Convert a byte offset into str to a character offset, so MatchData#begin
Expand Down Expand Up @@ -1131,6 +1134,26 @@ has_backslash(const char *s, mrb_int len)
return memchr(s, '\\', len) != NULL;
}

/* What sub and gsub build is the subject's bytes with the replacement spliced
in, so it is read the way the subject was; a replacement that was read as
bytes and goes above ASCII hands its reading over the way any appended
byte-read bytes do. A gsub that matched nothing spliced nothing, so its
result holds the subject alone and the replacement says nothing about it.
This is where CRuby lands on every pair it accepts. */
static void
re_mark_spliced(mrb_value result, mrb_value subject, mrb_value replacement,
mrb_bool spliced)
{
if (!re_binary_string_p(subject)) {
if (!spliced || !re_binary_string_p(replacement)) return;
const char *p = RSTRING_PTR(replacement);
const char *e = p + RSTRING_LEN(replacement);
while (p < e && !(*p & 0x80)) p++;
if (p == e) return;
}
mrb_str_ptr(result)->flags |= MRB_STR_BINARY;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/*
* Regexp.__gsub_str(re, str, replacement, checked = false) - gsub core without block
*
Expand Down Expand Up @@ -1222,6 +1245,7 @@ regexp_s_gsub_str(mrb_state *mrb, mrb_value klass)
clear_match_globals(mrb);
}

re_mark_spliced(result, str, replacement, last_ncap > 0);
return result;
}

Expand Down Expand Up @@ -1280,6 +1304,7 @@ regexp_s_sub_str(mrb_state *mrb, mrb_value klass)

create_matchdata(mrb, re, str, captures, cap_size);
mrb_free(mrb, captures);
re_mark_spliced(result, str, replacement, TRUE);
return result;
}

Expand Down
53 changes: 51 additions & 2 deletions mrbgems/mruby-regexp/test/regexp_utf8.rb
Original file line number Diff line number Diff line change
Expand Up @@ -543,12 +543,21 @@
# size-0 realloc and a write through NULL). See issue #6937.
# Patterns are always parsed as UTF-8, so build the bytes directly to
# exercise this in both MRB_UTF8_STRING and byte-string builds.
# append_as_bytes lays raw bytes into the string without moving how it is
# read; a sum of Integer#chr pieces would come back read as bytes, and a
# byte-read subject is answered by the byte on its own rather than the
# character it begins.
utf8 = ->(cp) {
s = ""
if cp < 0x800
(0xC0 | (cp >> 6)).chr + (0x80 | (cp & 0x3F)).chr
s.append_as_bytes(0xC0 | (cp >> 6))
s.append_as_bytes(0x80 | (cp & 0x3F))
else
(0xE0 | (cp >> 12)).chr + (0x80 | ((cp >> 6) & 0x3F)).chr + (0x80 | (cp & 0x3F)).chr
s.append_as_bytes(0xE0 | (cp >> 12))
s.append_as_bytes(0x80 | ((cp >> 6) & 0x3F))
s.append_as_bytes(0x80 | (cp & 0x3F))
end
s
}
s = "["
i = 0x80
Expand Down Expand Up @@ -646,3 +655,43 @@
assert_equal 2, ("あいb" =~ /b/)
assert_equal 1, ("a\u{10FFFF}b" =~ /b\z|\u{10FFFF}/)
end

assert("Regexp - a piece of a byte-read subject is byte-read") do
# What a match hands back is bytes of the subject, read the way the subject
# was. Encoding introspection lives in mruby-encoding, which this gem does
# not depend on, so ask only where it is present.
skip unless "".respond_to?(:encoding)
skip unless __ENCODING__ == "UTF-8"
subject = "a\x80b".b
assert_equal Encoding::BINARY, subject.match(/a/)[0].encoding
subject =~ /a/
assert_equal Encoding::BINARY, $~[0].encoding
assert_equal Encoding::BINARY, $&.encoding
assert_equal Encoding::BINARY, subject.scan(/./)[0].encoding
# a piece of a subject read as UTF-8 goes on reading as UTF-8
assert_equal Encoding::UTF_8, "あb".match(/b/)[0].encoding
end

assert("Regexp - what sub and gsub build out of a byte-read subject") do
# The result is the subject's bytes with the replacement spliced in, so it
# is read the way the subject was, and a replacement of byte-read bytes
# above ASCII marks a plain subject's result the way any appended byte-read
# bytes do.
skip unless "".respond_to?(:encoding)
skip unless __ENCODING__ == "UTF-8"
subject = "a\x80b".b
assert_equal Encoding::BINARY, subject.sub(/a/, "-").encoding
assert_equal Encoding::BINARY, subject.gsub(/a/, "-").encoding
assert_true subject.gsub(/a/, "-").valid_encoding?
assert_equal Encoding::BINARY, subject.gsub(/a/) { "-" }.encoding
assert_equal Encoding::BINARY, "ab".gsub(/a/, 171.chr).encoding
assert_equal Encoding::BINARY, "ab".sub(/a/, 171.chr).encoding
# a replacement of ASCII bytes moves nothing
assert_equal Encoding::UTF_8, "ab".gsub(/a/, "-".b).encoding
# and neither does one a search that matched nothing never spliced in
assert_equal Encoding::UTF_8, "ab".gsub(/x/, 171.chr).encoding
assert_equal Encoding::UTF_8, "ab".gsub("x", 171.chr).encoding
assert_equal Encoding::UTF_8, "ab".sub(/x/, 171.chr).encoding
# a byte-read subject is read as bytes whether anything was spliced or not
assert_equal Encoding::BINARY, subject.gsub(/x/, "-").encoding
end
Loading
Loading