mirror of
https://github.com/mistergibson/gxg-web-client.git
synced 2026-08-15 11:34:29 -07:00
2691 lines
60 KiB
Ruby
2691 lines
60 KiB
Ruby
class String
|
|
include Comparable
|
|
|
|
attr_reader :num_bytes
|
|
attr_reader_specific :num_bytes, :bytesize
|
|
|
|
attr_writer :encoding
|
|
attr_writer :ascii_only
|
|
attr_writer :valid_encoding
|
|
|
|
attr_accessor :data
|
|
|
|
alias_method :__data__, :data
|
|
alias_method :__data__=, :data=
|
|
|
|
def self.__allocate__
|
|
Rubinius.primitive :string_allocate
|
|
raise PrimitiveFailure, "String.allocate primitive failed"
|
|
end
|
|
|
|
def self.allocate
|
|
str = __allocate__
|
|
str.__data__ = Rubinius::ByteArray.allocate_sized(1)
|
|
str.num_bytes = 0
|
|
str
|
|
end
|
|
|
|
def self.from_codepoint(code, enc)
|
|
Rubinius.primitive :string_from_codepoint
|
|
raise PrimitiveFailure, "String.from_codepoint primitive failed"
|
|
end
|
|
|
|
def self.pattern(size, str)
|
|
Rubinius.primitive :string_pattern
|
|
raise PrimitiveFailure, "String.pattern primitive failed"
|
|
end
|
|
|
|
def ascii_only?
|
|
Rubinius.primitive :string_ascii_only_p
|
|
raise PrimitiveFailure, "String#ascii_only? primitive failed"
|
|
end
|
|
|
|
def encoding
|
|
Rubinius.primitive :string_encoding
|
|
raise PrimitiveFailure, "String#encoding primitive failed"
|
|
end
|
|
|
|
def ord
|
|
Rubinius.primitive :string_codepoint
|
|
raise ArgumentError, 'empty string' if empty?
|
|
raise ArgumentError, "invalid byte sequence in #{encoding}"
|
|
end
|
|
|
|
def chr_at(byte)
|
|
Rubinius.primitive :string_chr_at
|
|
raise ArgumentError, "String#chr_at primitive failed"
|
|
end
|
|
|
|
def to_f
|
|
Rubinius::Type::coerce_to_float self, false, false
|
|
end
|
|
|
|
alias_method :convert_float, :to_f
|
|
|
|
def __crypt__(other_str)
|
|
Rubinius.primitive :string_crypt
|
|
raise PrimitiveFailure, "String#crypt primitive failed"
|
|
end
|
|
|
|
def append(str)
|
|
Rubinius.primitive :string_append
|
|
raise TypeError, "String#append primitive only accepts Strings"
|
|
end
|
|
|
|
def byte_append(str)
|
|
Rubinius.primitive :string_byte_append
|
|
raise TypeError, "String#byte_append primitive only accepts Strings"
|
|
end
|
|
|
|
def byteslice(index_or_range, length=undefined)
|
|
Rubinius.primitive :string_byte_substring
|
|
|
|
if index_or_range.kind_of? Range
|
|
index = Rubinius::Type.coerce_to index_or_range.begin, Fixnum, :to_int
|
|
index += @num_bytes if index < 0
|
|
return if index < 0 or index > @num_bytes
|
|
|
|
finish = Rubinius::Type.coerce_to index_or_range.end, Fixnum, :to_int
|
|
finish += @num_bytes if finish < 0
|
|
|
|
finish += 1 unless index_or_range.exclude_end?
|
|
length = finish - index
|
|
|
|
return byteslice 0, 0 if length < 0
|
|
else
|
|
index = Rubinius::Type.coerce_to index_or_range, Fixnum, :to_int
|
|
index += @num_bytes if index < 0
|
|
|
|
if undefined.equal?(length)
|
|
return if index == @num_bytes
|
|
length = 1
|
|
else
|
|
length = Rubinius::Type.coerce_to length, Fixnum, :to_int
|
|
return if length < 0
|
|
end
|
|
|
|
return if index < 0 or index > @num_bytes
|
|
end
|
|
|
|
byteslice index, length
|
|
end
|
|
|
|
def dup
|
|
other = Rubinius.invoke_primitive :string_dup, self
|
|
Rubinius::Type.object_initialize_dup self, other
|
|
other
|
|
end
|
|
|
|
def clone
|
|
other = Rubinius.invoke_primitive :string_dup, self
|
|
Rubinius.invoke_primitive :object_copy_singleton_class, other, self
|
|
Rubinius::Type.object_initialize_clone self, other
|
|
other.freeze if frozen?
|
|
other
|
|
end
|
|
|
|
def substring(start, count)
|
|
Rubinius.primitive :string_substring
|
|
raise PrimitiveFailure, "String#substring primitive failed"
|
|
end
|
|
|
|
def ==(other)
|
|
Rubinius.primitive :string_equal
|
|
raise PrimitiveFailure, "String#== primitive failed"
|
|
end
|
|
|
|
def secure_compare(other)
|
|
Rubinius.primitive :string_secure_compare
|
|
|
|
if other.kind_of?(String)
|
|
raise PrimitiveFailure, "String#secure_compare primitive failed"
|
|
else
|
|
secure_compare StringValue(other)
|
|
end
|
|
end
|
|
|
|
def find_character(offset)
|
|
Rubinius.primitive :string_find_character
|
|
raise PrimitiveFailure, "String#find_character primitive failed"
|
|
end
|
|
|
|
def size
|
|
Rubinius.primitive :string_size
|
|
raise PrimitiveFailure, "String#size primitive failed"
|
|
end
|
|
|
|
alias_method :length, :size
|
|
|
|
# This is a work-in-progress. String is entirely coded around the idea of
|
|
# bytes, but we have to convert to the idea of characters, even for 1.8
|
|
# mode, where UTF-8, EUC, and SJIS will be represented properly as encoded
|
|
# strings rather than some ad hoc internal state. However, the byte
|
|
# characteristic of String is still important. More work remains.
|
|
def num_bytes=(bytes)
|
|
@num_chars = nil
|
|
@num_bytes = bytes
|
|
end
|
|
|
|
# In time, the JIT should be able to handle this as a ruby method.
|
|
def transform(tbl)
|
|
Rubinius.primitive :string_transform
|
|
raise PrimitiveFailure, "String#transform primitive failed"
|
|
end
|
|
|
|
def reverse!
|
|
Rubinius.primitive :string_reverse
|
|
raise PrimitiveFailure, "String#reverse primitive failed"
|
|
end
|
|
|
|
def valid_encoding?
|
|
Rubinius.primitive :string_valid_encoding_p
|
|
raise PrimitiveFailure, "String#valid_encoding? primitive failed"
|
|
end
|
|
|
|
##
|
|
# Creates a new string from copying _count_ bytes from the
|
|
# _start_ of _bytes_.
|
|
def self.from_bytearray(bytes, start, count)
|
|
Rubinius.primitive :string_from_bytearray
|
|
raise PrimitiveFailure, "String.from_bytearray primitive failed"
|
|
end
|
|
|
|
def self.try_convert(obj)
|
|
Rubinius::Type.try_convert obj, String, :to_str
|
|
end
|
|
|
|
class << self
|
|
def clone
|
|
raise TypeError, "Unable to clone/dup String class"
|
|
end
|
|
|
|
alias_method :dup, :clone
|
|
end
|
|
|
|
def initialize(arg = undefined, encoding: nil)
|
|
replace arg unless undefined.equal?(arg)
|
|
self.force_encoding(encoding) if encoding
|
|
self
|
|
end
|
|
|
|
private :initialize
|
|
|
|
def %(args)
|
|
*args = args
|
|
ret = Rubinius::Sprinter.get(self).call(*args)
|
|
|
|
Rubinius::Type.infect ret, self
|
|
end
|
|
|
|
def *(num)
|
|
num = Rubinius::Type.coerce_to(num, Integer, :to_int) unless num.kind_of? Integer
|
|
|
|
if num.kind_of? Bignum
|
|
raise RangeError, "bignum too big to convert into `long' (#{num})"
|
|
end
|
|
|
|
if num < 0
|
|
raise ArgumentError, "unable to multiple negative times (#{num})"
|
|
end
|
|
|
|
str = self.class.pattern num * @num_bytes, self
|
|
return str
|
|
end
|
|
|
|
def +(other)
|
|
other = StringValue(other)
|
|
Rubinius::Type.compatible_encoding self, other
|
|
String.new(self) << other
|
|
end
|
|
|
|
def ==(other)
|
|
Rubinius.primitive :string_equal
|
|
|
|
# Use #=== rather than #kind_of? because other might redefine kind_of?
|
|
unless String === other
|
|
if other.respond_to?(:to_str)
|
|
return other == self
|
|
end
|
|
return false
|
|
end
|
|
|
|
return false unless @num_bytes == other.bytesize
|
|
return false unless Encoding.compatible?(self, other)
|
|
return @data.compare_bytes(other.__data__, @num_bytes, other.bytesize) == 0
|
|
end
|
|
|
|
def =~(pattern)
|
|
case pattern
|
|
when Regexp
|
|
match_data = pattern.search_region(self, 0, @num_bytes, true)
|
|
Regexp.last_match = match_data
|
|
return match_data.begin(0) if match_data
|
|
when String
|
|
raise TypeError, "type mismatch: String given"
|
|
else
|
|
pattern =~ self
|
|
end
|
|
end
|
|
|
|
def [](index, other = undefined)
|
|
Rubinius.primitive :string_aref
|
|
|
|
unless undefined.equal?(other)
|
|
if index.kind_of?(Fixnum) && other.kind_of?(Fixnum)
|
|
return substring(index, other)
|
|
elsif index.kind_of? Regexp
|
|
match, str = subpattern(index, other)
|
|
Regexp.last_match = match
|
|
return str
|
|
else
|
|
length = Rubinius::Type.coerce_to(other, Fixnum, :to_int)
|
|
start = Rubinius::Type.coerce_to(index, Fixnum, :to_int)
|
|
return substring(start, length)
|
|
end
|
|
end
|
|
|
|
case index
|
|
when Regexp
|
|
match_data = index.search_region(self, 0, @num_bytes, true)
|
|
Regexp.last_match = match_data
|
|
if match_data
|
|
result = match_data.to_s
|
|
Rubinius::Type.infect result, index
|
|
return result
|
|
end
|
|
when String
|
|
return include?(index) ? index.dup : nil
|
|
when Range
|
|
start = Rubinius::Type.coerce_to index.first, Fixnum, :to_int
|
|
length = Rubinius::Type.coerce_to index.last, Fixnum, :to_int
|
|
|
|
start += size if start < 0
|
|
|
|
length += size if length < 0
|
|
length += 1 unless index.exclude_end?
|
|
|
|
return "" if start == size
|
|
return nil if start < 0 || start > size
|
|
|
|
length = size if length > size
|
|
length = length - start
|
|
length = 0 if length < 0
|
|
|
|
return substring(start, length)
|
|
else
|
|
index = Rubinius::Type.coerce_to index, Fixnum, :to_int
|
|
return self[index]
|
|
end
|
|
end
|
|
alias_method :slice, :[]
|
|
|
|
def capitalize
|
|
return dup if @num_bytes == 0
|
|
|
|
str = transform(Rubinius::CType::Lowered)
|
|
|
|
str.modify!
|
|
|
|
# Now do the actual capitalization
|
|
ba = str.__data__
|
|
ba[0] = Rubinius::CType.toupper(ba[0])
|
|
|
|
return str
|
|
end
|
|
|
|
def capitalize!
|
|
Rubinius.check_frozen
|
|
|
|
cap = capitalize()
|
|
return nil if cap == self
|
|
|
|
replace(cap)
|
|
return self
|
|
end
|
|
|
|
def casecmp(to)
|
|
to = StringValue(to)
|
|
order = @num_bytes - to.num_bytes
|
|
size = order < 0 ? @num_bytes : to.num_bytes
|
|
|
|
ctype = Rubinius::CType
|
|
|
|
i = 0
|
|
while i < size
|
|
a = @data[i]
|
|
b = to.__data__[i]
|
|
i += 1
|
|
|
|
a = ctype.toupper!(a) if ctype.islower(a)
|
|
b = ctype.toupper!(b) if ctype.islower(b)
|
|
r = a - b
|
|
|
|
next if r == 0
|
|
return r < 0 ? -1 : 1
|
|
end
|
|
|
|
return 0 if order == 0
|
|
return order < 0 ? -1 : 1
|
|
end
|
|
|
|
def chomp(separator=$/)
|
|
str = dup
|
|
str.chomp!(separator) || str
|
|
end
|
|
|
|
def chop
|
|
str = dup
|
|
str.chop! || str
|
|
end
|
|
|
|
def count(*strings)
|
|
raise ArgumentError, "wrong number of Arguments" if strings.empty?
|
|
|
|
return 0 if @num_bytes == 0
|
|
|
|
table = count_table(*strings).__data__
|
|
|
|
count = bytes = 0
|
|
while bytes < @num_bytes
|
|
count += 1 if table[@data[bytes]] == 1
|
|
bytes += find_character(bytes).num_bytes
|
|
end
|
|
|
|
count
|
|
end
|
|
|
|
def crypt(other_str)
|
|
other_str = StringValue(other_str)
|
|
|
|
if other_str.size < 2
|
|
raise ArgumentError, "salt must be at least 2 characters"
|
|
end
|
|
|
|
hash = __crypt__(other_str)
|
|
|
|
Rubinius::Type.infect hash, self
|
|
Rubinius::Type.infect hash, other_str
|
|
end
|
|
|
|
def delete(*strings)
|
|
str = dup
|
|
str.delete!(*strings) || str
|
|
end
|
|
|
|
def delete!(*strings)
|
|
raise ArgumentError, "wrong number of arguments" if strings.empty?
|
|
|
|
table = count_table(*strings).__data__
|
|
|
|
self.modify!
|
|
|
|
i = 0
|
|
j = -1
|
|
|
|
while i < @num_bytes
|
|
c = @data[i]
|
|
unless table[c] == 1
|
|
@data[j+=1] = c
|
|
end
|
|
i += 1
|
|
end
|
|
|
|
if (j += 1) < @num_bytes
|
|
self.num_bytes = j
|
|
self
|
|
else
|
|
nil
|
|
end
|
|
end
|
|
|
|
def downcase
|
|
return dup if @num_bytes == 0
|
|
transform(Rubinius::CType::Lowered)
|
|
end
|
|
|
|
def downcase!
|
|
Rubinius.check_frozen
|
|
|
|
return if @num_bytes == 0
|
|
|
|
str = transform(Rubinius::CType::Lowered)
|
|
|
|
return nil if str == self
|
|
|
|
replace(str)
|
|
|
|
return self
|
|
end
|
|
|
|
def each_char
|
|
return to_enum(:each_char) { size } unless block_given?
|
|
|
|
bytes = 0
|
|
while bytes < @num_bytes
|
|
char = find_character(bytes)
|
|
yield char
|
|
bytes += char.num_bytes
|
|
end
|
|
|
|
self
|
|
end
|
|
|
|
def each_byte
|
|
return to_enum(:each_byte) { bytesize } unless block_given?
|
|
i = 0
|
|
while i < @num_bytes do
|
|
yield @data.get_byte(i)
|
|
i += 1
|
|
end
|
|
self
|
|
end
|
|
|
|
def empty?
|
|
@num_bytes == 0
|
|
end
|
|
|
|
def end_with?(*suffixes)
|
|
suffixes.each do |suffix|
|
|
suffix = Rubinius::Type.check_convert_type suffix, String, :to_str
|
|
next unless suffix
|
|
|
|
return true if self[-suffix.length, suffix.length] == suffix
|
|
end
|
|
false
|
|
end
|
|
|
|
def eql?(other)
|
|
Rubinius.primitive :string_equal
|
|
|
|
return false unless other.kind_of?(String) && other.bytesize == @num_bytes
|
|
return false unless Encoding.compatible?(self, other)
|
|
return @data.compare_bytes(other.__data__, @num_bytes, other.bytesize) == 0
|
|
end
|
|
|
|
# This method is specifically part of 1.9 but we enable it in 1.8 also
|
|
# because we need it internally.
|
|
def getbyte(index)
|
|
index = Rubinius::Type.coerce_to index, Fixnum, :to_int
|
|
|
|
index += bytesize if index < 0
|
|
return if index < 0 or index >= bytesize
|
|
|
|
@data[index]
|
|
end
|
|
|
|
def include?(needle)
|
|
!!Rubinius::Mirror.reflect(self).find_string(StringValue(needle), 0)
|
|
end
|
|
|
|
ControlCharacters = [10, 9, 7, 11, 12, 13, 27, 8]
|
|
ControlPrintValue = ["\\n", "\\t", "\\a", "\\v", "\\f", "\\r", "\\e", "\\b"]
|
|
|
|
def lstrip
|
|
str = dup
|
|
str.lstrip! || str
|
|
end
|
|
|
|
def oct
|
|
to_inum(-8, false)
|
|
end
|
|
|
|
# Treats leading characters from <i>self</i> as a string of hexadecimal digits
|
|
# (with an optional sign and an optional <code>0x</code>) and returns the
|
|
# corresponding number. Zero is returned on error.
|
|
#
|
|
# "0x0a".hex #=> 10
|
|
# "-1234".hex #=> -4660
|
|
# "0".hex #=> 0
|
|
# "wombat".hex #=> 0
|
|
def hex
|
|
to_inum(16, false)
|
|
end
|
|
|
|
def reverse
|
|
dup.reverse!
|
|
end
|
|
|
|
def partition(pattern=nil)
|
|
return super() if pattern == nil && block_given?
|
|
|
|
if pattern.kind_of? Regexp
|
|
if m = pattern.match(self)
|
|
Regexp.last_match = m
|
|
return [m.pre_match, m.to_s, m.post_match]
|
|
end
|
|
else
|
|
pattern = StringValue(pattern)
|
|
if i = index(pattern)
|
|
post_start = i + pattern.length
|
|
post_len = size - post_start
|
|
|
|
return [substring(0, i),
|
|
pattern.dup,
|
|
substring(post_start, post_len)]
|
|
end
|
|
end
|
|
|
|
# Nothing worked out, this is the default.
|
|
return [self, "", ""]
|
|
end
|
|
|
|
def rpartition(pattern)
|
|
if pattern.kind_of? Regexp
|
|
if m = pattern.search_region(self, 0, size, false)
|
|
Regexp.last_match = m
|
|
[m.pre_match, m[0], m.post_match]
|
|
end
|
|
else
|
|
pattern = StringValue(pattern)
|
|
if i = rindex(pattern)
|
|
post_start = i + pattern.length
|
|
post_len = size - post_start
|
|
|
|
return [substring(0, i),
|
|
pattern.dup,
|
|
substring(post_start, post_len)]
|
|
end
|
|
|
|
# Nothing worked out, this is the default.
|
|
return ["", "", self]
|
|
end
|
|
end
|
|
|
|
def rstrip
|
|
str = dup
|
|
str.rstrip! || str
|
|
end
|
|
|
|
def scan(pattern)
|
|
taint = tainted? || pattern.tainted?
|
|
pattern = Rubinius::Type.coerce_to_regexp(pattern, true)
|
|
index = 0
|
|
|
|
last_match = nil
|
|
|
|
if block_given?
|
|
ret = self
|
|
else
|
|
ret = []
|
|
end
|
|
|
|
while match = pattern.match_from(self, index)
|
|
fin = match.full.at(1)
|
|
|
|
if match.collapsing?
|
|
if char = find_character(fin)
|
|
index = fin + char.bytesize
|
|
else
|
|
index = fin + 1
|
|
end
|
|
else
|
|
index = fin
|
|
end
|
|
|
|
last_match = match
|
|
val = (match.length == 1 ? match[0] : match.captures)
|
|
val.taint if taint
|
|
|
|
if block_given?
|
|
Regexp.last_match = match
|
|
yield(val)
|
|
else
|
|
ret << val
|
|
end
|
|
end
|
|
|
|
Regexp.last_match = last_match
|
|
return ret
|
|
end
|
|
|
|
# This method is specifically part of 1.9 but we enable it in 1.8 also
|
|
# because we need it internally.
|
|
def setbyte(index, byte)
|
|
self.modify!
|
|
|
|
index = Rubinius::Type.coerce_to index, Fixnum, :to_int
|
|
byte = Rubinius::Type.coerce_to byte, Fixnum, :to_int
|
|
|
|
index += bytesize if index < 0
|
|
if index < 0 or index >= bytesize
|
|
raise IndexError, "byte index #{index} is outside bounds of String"
|
|
end
|
|
|
|
@ascii_only = @valid_encoding = nil
|
|
@data[index] = byte
|
|
end
|
|
|
|
def split(pattern=nil, limit=undefined)
|
|
Rubinius::Splitter.split(self, pattern, limit)
|
|
end
|
|
|
|
def squeeze(*strings)
|
|
str = dup
|
|
str.squeeze!(*strings) || str
|
|
end
|
|
|
|
def squeeze!(*strings)
|
|
return if @num_bytes == 0
|
|
|
|
table = count_table(*strings).__data__
|
|
self.modify!
|
|
|
|
i = 1
|
|
j = 0
|
|
last = @data[0]
|
|
|
|
while i < @num_bytes
|
|
c = @data[i]
|
|
unless c == last and table[c] == 1
|
|
@data[j+=1] = last = c
|
|
end
|
|
i += 1
|
|
end
|
|
|
|
if (j += 1) < @num_bytes
|
|
self.num_bytes = j
|
|
self
|
|
else
|
|
nil
|
|
end
|
|
end
|
|
|
|
def delete_prefix(prefix)
|
|
prefix = Rubinius::Type.check_convert_type prefix, String, :to_str
|
|
return self[prefix.size..-1] if self.start_with?(prefix)
|
|
dup
|
|
end
|
|
|
|
def delete_prefix!(prefix)
|
|
Rubinius.check_frozen
|
|
result = delete_prefix(prefix)
|
|
return nil if result == self
|
|
|
|
replace(result)
|
|
self
|
|
end
|
|
|
|
def start_with?(*prefixes)
|
|
prefixes.each do |prefix|
|
|
prefix = Rubinius::Type.check_convert_type prefix, String, :to_str
|
|
next unless prefix
|
|
return true if self[0, prefix.length] == prefix
|
|
end
|
|
false
|
|
end
|
|
|
|
def strip
|
|
str = dup
|
|
str.strip! || str
|
|
end
|
|
|
|
def strip!
|
|
left = lstrip!
|
|
right = rstrip!
|
|
left.nil? && right.nil? ? nil : self
|
|
end
|
|
|
|
def succ
|
|
dup.succ!
|
|
end
|
|
|
|
def sum(bits=16)
|
|
bits = Rubinius::Type.coerce_to bits, Fixnum, :to_int
|
|
i = -1
|
|
sum = 0
|
|
|
|
sum += @data[i] while (i += 1) < @num_bytes
|
|
if bits > 0
|
|
sum & ((1 << bits) - 1)
|
|
else
|
|
sum
|
|
end
|
|
end
|
|
|
|
def swapcase
|
|
str = dup
|
|
str.swapcase! || str
|
|
end
|
|
|
|
def swapcase!
|
|
self.modify!
|
|
return if @num_bytes == 0
|
|
|
|
modified = false
|
|
|
|
ctype = Rubinius::CType
|
|
|
|
i = 0
|
|
while i < @num_bytes
|
|
c = @data[i]
|
|
if ctype.islower(c)
|
|
@data[i] = ctype.toupper!(c)
|
|
modified = true
|
|
elsif ctype.isupper(c)
|
|
@data[i] = ctype.tolower!(c)
|
|
modified = true
|
|
end
|
|
i += 1
|
|
end
|
|
|
|
modified ? self : nil
|
|
end
|
|
|
|
alias_method :intern, :to_sym
|
|
|
|
def to_i(base=10)
|
|
base = Rubinius::Type.coerce_to base, Integer, :to_int
|
|
|
|
if base < 0 || base == 1 || base > 36
|
|
raise ArgumentError, "illegal radix #{base}"
|
|
end
|
|
|
|
to_inum(base, false)
|
|
end
|
|
|
|
def to_s
|
|
instance_of?(String) ? self : "".replace(self)
|
|
end
|
|
alias_method :to_str, :to_s
|
|
|
|
def tr(source, replacement)
|
|
str = dup
|
|
str.tr!(source, replacement) || str
|
|
end
|
|
|
|
def tr!(source, replacement)
|
|
tr_trans(source, replacement, false)
|
|
end
|
|
|
|
def tr_s(source, replacement)
|
|
str = dup
|
|
str.tr_s!(source, replacement) || str
|
|
end
|
|
|
|
def tr_s!(source, replacement)
|
|
tr_trans(source, replacement, true)
|
|
end
|
|
|
|
def unpack(directives)
|
|
Rubinius.primitive :string_unpack
|
|
|
|
unless directives.kind_of? String
|
|
return unpack(StringValue(directives))
|
|
end
|
|
|
|
raise ArgumentError, "invalid directives string: #{directives}"
|
|
end
|
|
|
|
def upcase
|
|
str = dup
|
|
str.upcase! || str
|
|
end
|
|
|
|
def upcase!
|
|
return if @num_bytes == 0
|
|
self.modify!
|
|
|
|
modified = false
|
|
|
|
ctype = Rubinius::CType
|
|
|
|
i = 0
|
|
while i < @num_bytes
|
|
c = @data[i]
|
|
if ctype.islower(c)
|
|
@data[i] = ctype.toupper!(c)
|
|
modified = true
|
|
end
|
|
i += 1
|
|
end
|
|
|
|
modified ? self : nil
|
|
end
|
|
|
|
def to_sub_replacement(result, match)
|
|
index = 0
|
|
while index < @num_bytes
|
|
current = index
|
|
while current < @num_bytes && @data[current] != 92 # ?\\
|
|
current += 1
|
|
end
|
|
result.append(byteslice(index, current - index))
|
|
break if current == @num_bytes
|
|
|
|
# found backslash escape, looking next
|
|
if current == @num_bytes - 1
|
|
result.append("\\") # backslash at end of string
|
|
break
|
|
end
|
|
index = current + 1
|
|
|
|
cap = @data[index]
|
|
|
|
additional = case cap
|
|
when 38 # ?&
|
|
match[0]
|
|
when 96 # ?`
|
|
match.pre_match
|
|
when 39 # ?'
|
|
match.post_match
|
|
when 43 # ?+
|
|
match.captures.compact[-1].to_s
|
|
when 48..57 # ?0..?9
|
|
match[cap - 48].to_s
|
|
when 92 # ?\\ escaped backslash
|
|
'\\'
|
|
when 107 # \k named capture
|
|
if @data[index + 1] == 60
|
|
name = ""
|
|
i = index + 2
|
|
while i < @data.size && @data[i] != 62
|
|
name << @data[i]
|
|
i += 1
|
|
end
|
|
if i >= @data.size
|
|
'\\'.append(cap.chr)
|
|
index += 1
|
|
next
|
|
end
|
|
index = i
|
|
name.force_encoding result.encoding
|
|
match[name]
|
|
else
|
|
'\\'.append(cap.chr)
|
|
end
|
|
else # unknown escape
|
|
'\\'.append(cap.chr)
|
|
end
|
|
result.append(additional)
|
|
index += 1
|
|
end
|
|
end
|
|
|
|
def to_inum(base, check)
|
|
Rubinius.primitive :string_to_inum
|
|
raise ArgumentError, "invalid value for Integer"
|
|
end
|
|
|
|
def apply_and!(other)
|
|
Rubinius.primitive :string_apply_and
|
|
raise PrimitiveFailure, "String#apply_and! primitive failed"
|
|
end
|
|
|
|
def compare_substring(other, start, size)
|
|
Rubinius.primitive :string_compare_substring
|
|
|
|
if start > @num_bytes || start + @num_bytes < 0
|
|
raise IndexError, "index #{start} out of string"
|
|
end
|
|
raise PrimitiveFailure, "String#compare_substring primitive failed"
|
|
end
|
|
|
|
def count_table(*strings)
|
|
table = String.pattern 256, 1
|
|
|
|
i = 0
|
|
size = strings.size
|
|
while i < size
|
|
str = StringValue(strings[i]).dup
|
|
if str.bytesize > 1 && str.getbyte(0) == 94 # ?^
|
|
pos = 0
|
|
neg = 1
|
|
str.slice!(0)
|
|
else
|
|
pos = 1
|
|
neg = 0
|
|
end
|
|
|
|
set = String.pattern 256, neg
|
|
set_data = set.__data__
|
|
str.tr_expand! nil, true
|
|
str_data = str.__data__
|
|
j = -1
|
|
chars = str.bytesize
|
|
set_data[str_data[j]] = pos while (j += 1) < chars
|
|
|
|
table.apply_and! set
|
|
i += 1
|
|
end
|
|
table
|
|
end
|
|
|
|
def tr_expand!(limit, invalid_as_empty)
|
|
Rubinius.primitive :string_tr_expand
|
|
raise PrimitiveFailure, "String#tr_expand primitive failed"
|
|
end
|
|
|
|
# Unshares shared strings.
|
|
def modify!
|
|
Rubinius.check_frozen
|
|
|
|
if @shared
|
|
@data = @data.dup
|
|
@shared = nil
|
|
end
|
|
|
|
@ascii_only = @valid_encoding = nil
|
|
@hash_value = nil # reset the hash value
|
|
end
|
|
|
|
def subpattern(pattern, capture)
|
|
match = pattern.match(self)
|
|
|
|
return nil unless match
|
|
|
|
if index = Rubinius::Type.check_convert_type(capture, Fixnum, :to_int)
|
|
return nil if index >= match.size || -index >= match.size
|
|
capture = index
|
|
end
|
|
|
|
str = match[capture]
|
|
Rubinius::Type.infect str, pattern
|
|
[match, str]
|
|
end
|
|
private :subpattern
|
|
|
|
def prefix?(other)
|
|
size = other.size
|
|
return false if size > @num_bytes
|
|
other.compare_substring(self, 0, size) == 0
|
|
end
|
|
|
|
def suffix?(other)
|
|
size = other.size
|
|
return false if size > @num_bytes
|
|
other.compare_substring(self, -size, size) == 0
|
|
end
|
|
|
|
def shorten!(size)
|
|
self.modify!
|
|
return if @num_bytes == 0
|
|
self.num_bytes -= size
|
|
end
|
|
|
|
def shared!
|
|
@shared = true
|
|
end
|
|
|
|
def each_codepoint
|
|
return to_enum(:each_codepoint) { size } unless block_given?
|
|
|
|
each_char { |c| yield c.ord }
|
|
self
|
|
end
|
|
|
|
def b
|
|
dup.force_encoding Encoding::ASCII_8BIT
|
|
end
|
|
|
|
def bytes
|
|
if block_given?
|
|
each_byte do |byte|
|
|
yield byte
|
|
end
|
|
else
|
|
each_byte.to_a
|
|
end
|
|
end
|
|
|
|
def chars
|
|
if block_given?
|
|
each_char do |char|
|
|
yield char
|
|
end
|
|
else
|
|
each_char.to_a
|
|
end
|
|
end
|
|
|
|
def codepoints
|
|
if block_given?
|
|
each_codepoint do |codepoint|
|
|
yield codepoint
|
|
end
|
|
else
|
|
each_codepoint.to_a
|
|
end
|
|
end
|
|
|
|
def encode!(to=undefined, from=undefined, options=undefined)
|
|
Rubinius.check_frozen
|
|
|
|
case to
|
|
when Encoding
|
|
to_enc = to
|
|
when Hash
|
|
options = to
|
|
to_enc = Encoding.default_internal
|
|
when undefined
|
|
to_enc = Encoding.default_internal
|
|
return self unless to_enc
|
|
else
|
|
opts = Rubinius::Type::check_convert_type to, Hash, :to_hash
|
|
|
|
if opts
|
|
options = opts
|
|
to_enc = Encoding.default_internal
|
|
else
|
|
to_enc = Rubinius::Type.try_convert_to_encoding to
|
|
end
|
|
end
|
|
|
|
case from
|
|
when undefined
|
|
from_enc = encoding
|
|
when Encoding
|
|
from_enc = from
|
|
when Hash
|
|
options = from
|
|
from_enc = encoding
|
|
else
|
|
opts = Rubinius::Type::check_convert_type from, Hash, :to_hash
|
|
|
|
if opts
|
|
options = opts
|
|
from_enc = encoding
|
|
else
|
|
from_enc = Rubinius::Type.coerce_to_encoding from
|
|
end
|
|
end
|
|
|
|
if undefined.equal? from_enc or undefined.equal? to_enc
|
|
raise Encoding::ConverterNotFoundError, "undefined code converter (#{from} to #{to})"
|
|
end
|
|
|
|
case options
|
|
when undefined
|
|
options = 0
|
|
when Hash
|
|
# do nothing
|
|
else
|
|
options = Rubinius::Type.coerce_to options, Hash, :to_hash
|
|
end
|
|
|
|
# TODO: Only UTF-8 is allowed for internal encoding. This is a preliminary
|
|
# step to fixing all encoding-related interfaces.
|
|
to_enc = Encoding::UTF_8 unless to_enc == Encoding::UTF_8
|
|
|
|
if ascii_only? and from_enc.ascii_compatible? and to_enc and to_enc.ascii_compatible?
|
|
force_encoding to_enc
|
|
elsif to_enc and from_enc != to_enc
|
|
ec = Encoding::Converter.new from_enc, to_enc, options
|
|
dest = ""
|
|
status = ec.primitive_convert self.dup, dest, nil, nil, ec.options
|
|
raise ec.last_error unless status == :finished
|
|
replace dest
|
|
end
|
|
|
|
# TODO: replace this hack with transcoders
|
|
if options.kind_of? Hash
|
|
case xml = options[:xml]
|
|
when :text
|
|
gsub!(/[&><]/, '&' => '&', '>' => '>', '<' => '<')
|
|
when :attr
|
|
gsub!(/[&><"]/, '&' => '&', '>' => '>', '<' => '<', '"' => '"')
|
|
insert(0, '"')
|
|
insert(-1, '"')
|
|
when nil
|
|
# nothing
|
|
else
|
|
raise ArgumentError, "unexpected value for xml option: #{xml.inspect}"
|
|
end
|
|
|
|
if options[:universal_newline]
|
|
gsub!(/\r\n|\r/, "\r\n" => "\n", "\r" => "\n")
|
|
end
|
|
end
|
|
|
|
self
|
|
end
|
|
|
|
def encode(to=undefined, from=undefined, options=undefined)
|
|
dup.encode! to, from, options
|
|
end
|
|
|
|
def end_with?(*suffixes)
|
|
suffixes.each do |original_suffix|
|
|
suffix = Rubinius::Type.check_convert_type original_suffix, String, :to_str
|
|
unless suffix
|
|
raise TypeError, "no implicit conversion of #{original_suffix.class} into String"
|
|
end
|
|
return true if self[-suffix.length, suffix.length] == suffix
|
|
end
|
|
false
|
|
end
|
|
|
|
def force_encoding(enc)
|
|
enc = Rubinius::Type.coerce_to_encoding enc
|
|
|
|
# TODO: Only UTF-8 encodings are supported internally.
|
|
return self unless enc.equal?(Encoding::UTF_8) or enc.equal?(Encoding::BINARY)
|
|
|
|
@encoding = enc
|
|
|
|
unless @ascii_only && @encoding.ascii_compatible?
|
|
@ascii_only = @valid_encoding = @num_chars = nil
|
|
end
|
|
if bytesize == 0 && @encoding.ascii_compatible?
|
|
@ascii_only = true
|
|
@valid_encoding = true
|
|
@num_chars = 0
|
|
end
|
|
|
|
self
|
|
end
|
|
|
|
def inspect
|
|
result_encoding = Encoding.default_internal || Encoding.default_external
|
|
unless result_encoding.ascii_compatible?
|
|
result_encoding = Encoding::US_ASCII
|
|
end
|
|
|
|
enc = encoding
|
|
ascii = enc.ascii_compatible?
|
|
enc_name = enc.name
|
|
unicode = enc_name.start_with?("UTF-") && enc_name[4] != ?7
|
|
|
|
if unicode
|
|
if enc.equal? Encoding::UTF_16
|
|
a = getbyte 0
|
|
b = getbyte 1
|
|
|
|
if a == 0xfe and b == 0xff
|
|
enc = Encoding::UTF_16BE
|
|
elsif a == 0xff and b == 0xfe
|
|
enc = Encoding::UTF_16LE
|
|
else
|
|
unicode = false
|
|
end
|
|
elsif enc.equal? Encoding::UTF_32
|
|
a = getbyte 0
|
|
b = getbyte 1
|
|
c = getbyte 2
|
|
d = getbyte 3
|
|
|
|
if a == 0 and b == 0 and c == 0xfe and d == 0xfe
|
|
enc = Encoding::UTF_32BE
|
|
elsif a == 0xff and b == 0xfe and c == 0 and d == 0
|
|
enc = Encoding::UTF_32LE
|
|
else
|
|
unicode = false
|
|
end
|
|
end
|
|
end
|
|
|
|
array = []
|
|
|
|
index = 0
|
|
total = bytesize
|
|
while index < total
|
|
char = chr_at index
|
|
|
|
if char
|
|
bs = char.bytesize
|
|
|
|
if (ascii or unicode) and bs == 1
|
|
escaped = nil
|
|
|
|
byte = getbyte(index)
|
|
if byte >= 7 and byte <= 92
|
|
case byte
|
|
when 7 # \a
|
|
escaped = '\a'
|
|
when 8 # \b
|
|
escaped = '\b'
|
|
when 9 # \t
|
|
escaped = '\t'
|
|
when 10 # \n
|
|
escaped = '\n'
|
|
when 11 # \v
|
|
escaped = '\v'
|
|
when 12 # \f
|
|
escaped = '\f'
|
|
when 13 # \r
|
|
escaped = '\r'
|
|
when 27 # \e
|
|
escaped = '\e'
|
|
when 34 # \"
|
|
escaped = '\"'
|
|
when 35 # #
|
|
case getbyte(index += 1)
|
|
when 36 # $
|
|
escaped = '\#$'
|
|
when 64 # @
|
|
escaped = '\#@'
|
|
when 123 # {
|
|
escaped = '\#{'
|
|
else
|
|
index -= 1
|
|
end
|
|
when 92 # \\
|
|
escaped = '\\\\'
|
|
end
|
|
|
|
if escaped
|
|
array << escaped
|
|
index += 1
|
|
next
|
|
end
|
|
end
|
|
end
|
|
|
|
if char.printable?
|
|
array << char
|
|
else
|
|
code = char.ord
|
|
escaped = code.to_s(16).upcase
|
|
|
|
if unicode
|
|
if code < 0x10000
|
|
pad = "0" * (4 - escaped.bytesize)
|
|
array << "\\u#{pad}#{escaped}"
|
|
else
|
|
array << "\\u{#{escaped}}"
|
|
end
|
|
else
|
|
if code < 0x100
|
|
pad = "0" * (2 - escaped.bytesize)
|
|
array << "\\x#{pad}#{escaped}"
|
|
else
|
|
array << "\\x{#{escaped}}"
|
|
end
|
|
end
|
|
end
|
|
|
|
index += bs
|
|
else
|
|
array << "\\x#{getbyte(index).to_s(16)}"
|
|
index += 1
|
|
end
|
|
end
|
|
|
|
size = array.inject(0) { |s, chr| s += chr.bytesize }
|
|
result = String.pattern size + 2, ?".ord
|
|
m = Rubinius::Mirror.reflect result
|
|
|
|
index = 1
|
|
array.each do |chr|
|
|
m.copy_from chr, 0, chr.bytesize, index
|
|
index += chr.bytesize
|
|
end
|
|
|
|
Rubinius::Type.infect result, self
|
|
result.force_encoding result_encoding
|
|
end
|
|
|
|
def prepend(other)
|
|
self[0, 0] = other
|
|
self
|
|
end
|
|
|
|
def upto(stop, exclusive=false)
|
|
return to_enum :upto, stop, exclusive unless block_given?
|
|
stop = StringValue(stop)
|
|
|
|
if stop.size == 1 && size == 1
|
|
return self if self > stop
|
|
after_stop = stop.getbyte(0) + (exclusive ? 0 : 1)
|
|
current = getbyte(0)
|
|
until current == after_stop
|
|
yield current.chr
|
|
current += 1
|
|
end
|
|
else
|
|
unless stop.size < size
|
|
after_stop = exclusive ? stop : stop.succ
|
|
current = self
|
|
|
|
until current == after_stop
|
|
yield current
|
|
current = StringValue(current.succ)
|
|
break if current.size > stop.size || current.size == 0
|
|
end
|
|
end
|
|
end
|
|
self
|
|
end
|
|
|
|
def sub(pattern, replacement=undefined)
|
|
# Because of the behavior of $~, this is duplicated from sub! because
|
|
# if we call sub! from sub, the last_match can't be updated properly.
|
|
|
|
unless valid_encoding?
|
|
raise ArgumentError, "invalid byte sequence in #{encoding}"
|
|
end
|
|
|
|
ret = byteslice(0, 0) # Empty string and string subclass
|
|
|
|
if undefined.equal? replacement
|
|
unless block_given?
|
|
raise ArgumentError, "method '#{__method__}': given 1, expected 2"
|
|
end
|
|
use_yield = true
|
|
else
|
|
unless replacement.kind_of?(String)
|
|
hash = Rubinius::Type.check_convert_type(replacement, Hash, :to_hash)
|
|
replacement = StringValue(replacement) unless hash
|
|
end
|
|
use_yield = false
|
|
|
|
Rubinius::Type.infect ret, replacement
|
|
end
|
|
|
|
pattern = Rubinius::Type.coerce_to_regexp(pattern, true) unless pattern.kind_of? Regexp
|
|
match = pattern.match_from(self, 0)
|
|
|
|
Regexp.last_match = match
|
|
|
|
if match
|
|
ret.append match.pre_match
|
|
|
|
if use_yield || hash
|
|
Regexp.last_match = match
|
|
|
|
if use_yield
|
|
val = yield match.to_s
|
|
else
|
|
val = hash[match.to_s]
|
|
end
|
|
val = val.to_s unless val.kind_of?(String)
|
|
|
|
Rubinius::Type.infect ret, val
|
|
|
|
ret.append val
|
|
else
|
|
replacement.to_sub_replacement(ret, match)
|
|
end
|
|
|
|
Rubinius::Type.infect ret, val
|
|
ret.append(match.post_match)
|
|
else
|
|
ret = dup
|
|
end
|
|
|
|
ret
|
|
end
|
|
|
|
def sub!(pattern, replacement=undefined)
|
|
# Because of the behavior of $~, this is duplicated from sub! because
|
|
# if we call sub! from sub, the last_match can't be updated properly.
|
|
|
|
unless valid_encoding?
|
|
raise ArgumentError, "invalid byte sequence in #{encoding}"
|
|
end
|
|
|
|
ret = byteslice(0, 0) # Empty string and string subclass
|
|
|
|
if undefined.equal? replacement
|
|
unless block_given?
|
|
raise ArgumentError, "method '#{__method__}': given 1, expected 2"
|
|
end
|
|
Rubinius.check_frozen
|
|
use_yield = true
|
|
else
|
|
Rubinius.check_frozen
|
|
|
|
unless replacement.kind_of?(String)
|
|
hash = Rubinius::Type.check_convert_type(replacement, Hash, :to_hash)
|
|
replacement = StringValue(replacement) unless hash
|
|
end
|
|
use_yield = false
|
|
|
|
Rubinius::Type.infect ret, replacement
|
|
end
|
|
|
|
pattern = Rubinius::Type.coerce_to_regexp(pattern, true) unless pattern.kind_of? Regexp
|
|
match = pattern.match_from(self, 0)
|
|
|
|
Regexp.last_match = match
|
|
|
|
if match
|
|
ret.append match.pre_match
|
|
|
|
if use_yield || hash
|
|
Regexp.last_match = match
|
|
|
|
if use_yield
|
|
val = yield match.to_s
|
|
else
|
|
val = hash[match.to_s]
|
|
end
|
|
val = val.to_s unless val.kind_of?(String)
|
|
|
|
Rubinius::Type.infect ret, val
|
|
|
|
ret.append val
|
|
else
|
|
replacement.to_sub_replacement(ret, match)
|
|
end
|
|
|
|
Rubinius::Type.infect ret, val
|
|
|
|
ret.append(match.post_match)
|
|
else
|
|
return nil
|
|
end
|
|
|
|
replace(ret)
|
|
self
|
|
end
|
|
|
|
def slice!(one, two=undefined)
|
|
Rubinius.check_frozen
|
|
# This is un-DRY, but it's a simple manual argument splitting. Keeps
|
|
# the code fast and clean since the sequence are pretty short.
|
|
#
|
|
if undefined.equal?(two)
|
|
result = slice(one)
|
|
|
|
if one.kind_of? Regexp
|
|
lm = Regexp.last_match
|
|
self[one] = '' if result
|
|
Regexp.last_match = lm
|
|
else
|
|
self[one] = '' if result
|
|
end
|
|
else
|
|
result = slice(one, two)
|
|
|
|
if one.kind_of? Regexp
|
|
lm = Regexp.last_match
|
|
self[one, two] = '' if result
|
|
Regexp.last_match = lm
|
|
else
|
|
self[one, two] = '' if result
|
|
end
|
|
end
|
|
|
|
result
|
|
end
|
|
|
|
# TODO: make encoding aware.
|
|
def succ!
|
|
self.modify!
|
|
|
|
return self if @num_bytes == 0
|
|
|
|
carry = nil
|
|
last_alnum = 0
|
|
start = @num_bytes - 1
|
|
|
|
ctype = Rubinius::CType
|
|
|
|
while start >= 0
|
|
s = @data[start]
|
|
if ctype.isalnum(s)
|
|
carry = 0
|
|
if (48 <= s && s < 57) ||
|
|
(97 <= s && s < 122) ||
|
|
(65 <= s && s < 90)
|
|
@data[start] += 1
|
|
elsif s == 57
|
|
@data[start] = 48
|
|
carry = 49
|
|
elsif s == 122
|
|
@data[start] = carry = 97
|
|
elsif s == 90
|
|
@data[start] = carry = 65
|
|
end
|
|
|
|
break if carry == 0
|
|
last_alnum = start
|
|
end
|
|
|
|
start -= 1
|
|
end
|
|
|
|
if carry.nil?
|
|
start = length - 1
|
|
carry = 1
|
|
|
|
while start >= 0
|
|
if @data[start] >= 255
|
|
@data[start] = 0
|
|
else
|
|
@data[start] += 1
|
|
break
|
|
end
|
|
|
|
start -= 1
|
|
end
|
|
end
|
|
|
|
if start < 0
|
|
m = Rubinius::Mirror.reflect self
|
|
m.splice last_alnum, 1, carry.chr + @data[last_alnum].chr
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
alias_method :next, :succ
|
|
alias_method :next!, :succ!
|
|
|
|
def to_c
|
|
Complexifier.new(self).convert
|
|
end
|
|
|
|
def to_r
|
|
Rationalizer.new(self).convert
|
|
end
|
|
|
|
def rstrip!
|
|
Rubinius.check_frozen
|
|
return if @num_bytes == 0
|
|
|
|
stop = @num_bytes - 1
|
|
|
|
ctype = Rubinius::CType
|
|
|
|
while stop >= 0 && (@data[stop] == 0 || ctype.isspace(@data[stop]))
|
|
stop -= 1
|
|
end
|
|
|
|
return if (stop += 1) == @num_bytes
|
|
|
|
modify!
|
|
self.num_bytes = stop
|
|
self
|
|
end
|
|
|
|
def lstrip!
|
|
Rubinius.check_frozen
|
|
return if @num_bytes == 0
|
|
|
|
start = 0
|
|
|
|
ctype = Rubinius::CType
|
|
|
|
while start < @num_bytes && ctype.isspace(@data[start])
|
|
start += 1
|
|
end
|
|
|
|
return if start == 0
|
|
|
|
modify!
|
|
self.num_bytes -= start
|
|
@data.move_bytes start, @num_bytes, 0
|
|
self
|
|
end
|
|
|
|
def chop!
|
|
Rubinius.check_frozen
|
|
|
|
m = Rubinius::Mirror.reflect self
|
|
|
|
bytes = m.previous_byte_index @num_bytes
|
|
return unless bytes
|
|
|
|
chr = chr_at bytes
|
|
if chr.ord == 10 and chr.ascii?
|
|
if i = m.previous_byte_index(bytes)
|
|
chr = chr_at i
|
|
|
|
bytes = i if chr.ord == 13 and chr.ascii?
|
|
end
|
|
end
|
|
|
|
self.num_bytes = bytes
|
|
|
|
# We do not need to dup the data, so don't use #modify!
|
|
@hash_value = nil
|
|
|
|
self
|
|
end
|
|
|
|
def chomp!(sep=undefined)
|
|
Rubinius.check_frozen
|
|
|
|
if undefined.equal?(sep)
|
|
sep = $/
|
|
elsif sep
|
|
sep = StringValue(sep)
|
|
end
|
|
|
|
return if sep.nil?
|
|
|
|
m = Rubinius::Mirror.reflect self
|
|
|
|
if sep == DEFAULT_RECORD_SEPARATOR
|
|
return unless bytes = m.previous_byte_index(@num_bytes)
|
|
|
|
chr = chr_at bytes
|
|
return unless chr.ascii?
|
|
|
|
case chr.ord
|
|
when 13
|
|
# do nothing
|
|
when 10
|
|
if j = m.previous_byte_index(bytes)
|
|
chr = chr_at j
|
|
|
|
if chr.ord == 13 and chr.ascii?
|
|
bytes = j
|
|
end
|
|
end
|
|
else
|
|
return
|
|
end
|
|
elsif sep.size == 0
|
|
return if @num_bytes == 0
|
|
bytes = @num_bytes
|
|
|
|
while i = m.previous_byte_index(bytes)
|
|
chr = chr_at i
|
|
break unless chr.ord == 10 and chr.ascii?
|
|
|
|
bytes = i
|
|
|
|
if j = m.previous_byte_index(i)
|
|
chr = chr_at j
|
|
if chr.ord == 13 and chr.ascii?
|
|
bytes = j
|
|
end
|
|
end
|
|
end
|
|
|
|
return if bytes == @num_bytes
|
|
else
|
|
size = sep.size
|
|
return if size > @num_bytes
|
|
|
|
# TODO: Move #compare_substring to mirror.
|
|
return unless sep.compare_substring(self, -size, size) == 0
|
|
bytes = @num_bytes - size
|
|
end
|
|
|
|
# We do not need to dup the data, so don't use #modify!
|
|
@hash_value = nil
|
|
self.num_bytes = bytes
|
|
|
|
self
|
|
end
|
|
|
|
def clear
|
|
Rubinius.check_frozen
|
|
self.num_bytes = 0
|
|
self
|
|
end
|
|
|
|
def replace(other)
|
|
Rubinius.check_frozen
|
|
|
|
# If we're replacing with ourselves, then we have nothing to do
|
|
return self if Rubinius::Type.object_equal(self, other)
|
|
|
|
other = StringValue(other)
|
|
|
|
@shared = true
|
|
if other.frozen?
|
|
@data = other.__data__.dup
|
|
else
|
|
other.shared!
|
|
@data = other.__data__
|
|
end
|
|
self.num_bytes = other.num_bytes
|
|
@hash_value = nil
|
|
force_encoding(other.encoding)
|
|
@valid_encoding = other.valid_encoding?
|
|
@ascii_only = nil
|
|
|
|
Rubinius::Type.infect(self, other)
|
|
end
|
|
|
|
def initialize_copy(other)
|
|
end
|
|
|
|
private :initialize_copy
|
|
|
|
def <<(other)
|
|
Rubinius.check_frozen
|
|
|
|
unless other.kind_of? String
|
|
if other.kind_of? Integer
|
|
if encoding == Encoding::US_ASCII and other >= 128 and other < 256
|
|
force_encoding(Encoding::ASCII_8BIT)
|
|
end
|
|
|
|
other = other.chr(encoding)
|
|
else
|
|
other = StringValue(other)
|
|
end
|
|
end
|
|
|
|
Rubinius::Type.infect(self, other)
|
|
append(other)
|
|
end
|
|
alias_method :concat, :<<
|
|
|
|
def chr
|
|
substring 0, 1
|
|
end
|
|
|
|
def each_line(sep=$/)
|
|
return to_enum(:each_line, sep) unless block_given?
|
|
|
|
# weird edge case.
|
|
if sep.nil?
|
|
yield self
|
|
return self
|
|
end
|
|
|
|
sep = StringValue(sep)
|
|
|
|
pos = 0
|
|
|
|
size = @num_bytes
|
|
orig_data = @data
|
|
|
|
# If the separator is empty, we're actually in paragraph mode. This
|
|
# is used so infrequently, we'll handle it completely separately from
|
|
# normal line breaking.
|
|
if sep.empty?
|
|
sep = "\n\n"
|
|
pat_size = 2
|
|
m = Rubinius::Mirror.reflect self
|
|
|
|
while pos < size
|
|
nxt = m.find_string(sep, pos)
|
|
break unless nxt
|
|
|
|
while @data[nxt] == 10 and nxt < @num_bytes
|
|
nxt += 1
|
|
end
|
|
|
|
match_size = nxt - pos
|
|
|
|
# string ends with \n's
|
|
break if pos == @num_bytes
|
|
|
|
str = byteslice pos, match_size
|
|
yield str unless str.empty?
|
|
|
|
# detect mutation within the block
|
|
if !@data.equal?(orig_data) or @num_bytes != size
|
|
raise RuntimeError, "string modified while iterating"
|
|
end
|
|
|
|
pos = nxt
|
|
end
|
|
|
|
# No more separates, but we need to grab the last part still.
|
|
fin = byteslice pos, @num_bytes - pos
|
|
yield fin if fin and !fin.empty?
|
|
|
|
else
|
|
|
|
# This is the normal case.
|
|
pat_size = sep.size
|
|
unmodified_self = clone
|
|
m = Rubinius::Mirror.reflect unmodified_self
|
|
|
|
while pos < size
|
|
nxt = m.find_string(sep, pos)
|
|
break unless nxt
|
|
|
|
match_size = nxt - pos
|
|
str = unmodified_self.byteslice pos, match_size + pat_size
|
|
yield str unless str.empty?
|
|
|
|
pos = nxt + pat_size
|
|
end
|
|
|
|
# No more separates, but we need to grab the last part still.
|
|
fin = unmodified_self.byteslice pos, @num_bytes - pos
|
|
yield fin unless fin.empty?
|
|
end
|
|
|
|
self
|
|
end
|
|
|
|
def lines(sep=$/)
|
|
if block_given?
|
|
each_line(sep) do |line|
|
|
yield line
|
|
end
|
|
else
|
|
each_line(sep).to_a
|
|
end
|
|
end
|
|
|
|
|
|
def gsub(pattern, replacement=undefined)
|
|
# Because of the behavior of $~, this is duplicated from gsub! because
|
|
# if we call gsub! from gsub, the last_match can't be updated properly.
|
|
|
|
unless valid_encoding?
|
|
raise ArgumentError, "invalid byte sequence in #{encoding}"
|
|
end
|
|
|
|
ret = byteslice(0, 0) # Empty string and string subclass
|
|
|
|
if undefined.equal? replacement
|
|
unless block_given?
|
|
return to_enum(:gsub, pattern, replacement)
|
|
end
|
|
use_yield = true
|
|
else
|
|
unless replacement.kind_of?(String)
|
|
hash = Rubinius::Type.check_convert_type(replacement, Hash, :to_hash)
|
|
replacement = StringValue(replacement) unless hash
|
|
end
|
|
use_yield = false
|
|
|
|
Rubinius::Type.infect ret, replacement
|
|
end
|
|
|
|
pattern = Rubinius::Type.coerce_to_regexp(pattern, true) unless pattern.kind_of? Regexp
|
|
match = pattern.search_region(self, 0, @num_bytes, true)
|
|
|
|
unless match
|
|
Regexp.last_match = nil
|
|
end
|
|
|
|
orig_len = @num_bytes
|
|
orig_data = @data
|
|
|
|
last_end = 0
|
|
offset = nil
|
|
|
|
last_match = nil
|
|
|
|
offset = match.full.at(0) if match
|
|
|
|
while match
|
|
if str = match.pre_match_from(last_end)
|
|
ret.append str
|
|
end
|
|
|
|
if use_yield || hash
|
|
Regexp.last_match = match
|
|
|
|
if use_yield
|
|
val = yield match.to_s
|
|
else
|
|
val = hash[match.to_s]
|
|
end
|
|
val = val.to_s unless val.kind_of?(String)
|
|
|
|
Rubinius::Type.infect ret, val
|
|
|
|
ret.append val
|
|
|
|
if !@data.equal?(orig_data) or @num_bytes != orig_len
|
|
raise RuntimeError, "string modified"
|
|
end
|
|
else
|
|
replacement.to_sub_replacement(ret, match)
|
|
end
|
|
|
|
Rubinius::Type.infect ret, val
|
|
|
|
last_end = match.full.at(1)
|
|
|
|
if match.collapsing?
|
|
if char = find_character(offset)
|
|
offset += char.bytesize
|
|
else
|
|
offset += 1
|
|
end
|
|
else
|
|
offset = match.full.at(1)
|
|
end
|
|
|
|
last_match = match
|
|
|
|
match = pattern.match_from self, offset
|
|
break unless match
|
|
|
|
offset = match.full.at(0)
|
|
end
|
|
|
|
Regexp.last_match = last_match
|
|
|
|
str = byteslice(last_end, @num_bytes-last_end+1)
|
|
if str
|
|
ret.append str
|
|
end
|
|
|
|
ret
|
|
end
|
|
|
|
def gsub!(pattern, replacement=undefined)
|
|
# Because of the behavior of $~, this is duplicated from gsub! because
|
|
# if we call gsub! from gsub, the last_match can't be updated properly.
|
|
|
|
unless valid_encoding?
|
|
raise ArgumentError, "invalid byte sequence in #{encoding}"
|
|
end
|
|
|
|
ret = byteslice(0, 0) # Empty string and string subclass
|
|
|
|
if undefined.equal? replacement
|
|
unless block_given?
|
|
return to_enum(:gsub, pattern, replacement)
|
|
end
|
|
Rubinius.check_frozen
|
|
use_yield = true
|
|
else
|
|
Rubinius.check_frozen
|
|
|
|
unless replacement.kind_of?(String)
|
|
hash = Rubinius::Type.check_convert_type(replacement, Hash, :to_hash)
|
|
replacement = StringValue(replacement) unless hash
|
|
end
|
|
use_yield = false
|
|
|
|
Rubinius::Type.infect ret, replacement
|
|
end
|
|
|
|
pattern = Rubinius::Type.coerce_to_regexp(pattern, true) unless pattern.kind_of? Regexp
|
|
match = pattern.search_region(self, 0, @num_bytes, true)
|
|
|
|
unless match
|
|
Regexp.last_match = nil
|
|
return nil
|
|
end
|
|
|
|
orig_len = @num_bytes
|
|
orig_data = @data
|
|
|
|
last_end = 0
|
|
offset = nil
|
|
|
|
last_match = nil
|
|
|
|
offset = match.full.at(0)
|
|
|
|
while match
|
|
if str = match.pre_match_from(last_end)
|
|
ret.append str
|
|
end
|
|
|
|
if use_yield || hash
|
|
Regexp.last_match = match
|
|
|
|
if use_yield
|
|
val = yield match.to_s
|
|
else
|
|
val = hash[match.to_s]
|
|
end
|
|
val = val.to_s unless val.kind_of?(String)
|
|
|
|
Rubinius::Type.infect ret, val
|
|
|
|
ret.append val
|
|
|
|
if !@data.equal?(orig_data) or @num_bytes != orig_len
|
|
raise RuntimeError, "string modified"
|
|
end
|
|
else
|
|
replacement.to_sub_replacement(ret, match)
|
|
end
|
|
|
|
Rubinius::Type.infect ret, val
|
|
|
|
last_end = match.full.at(1)
|
|
|
|
if match.collapsing?
|
|
if char = find_character(offset)
|
|
offset += char.bytesize
|
|
else
|
|
offset += 1
|
|
end
|
|
else
|
|
offset = match.full.at(1)
|
|
end
|
|
|
|
last_match = match
|
|
|
|
match = pattern.match_from self, offset
|
|
break unless match
|
|
|
|
offset = match.full.at(0)
|
|
end
|
|
|
|
Regexp.last_match = last_match
|
|
|
|
str = byteslice(last_end, @num_bytes-last_end+1)
|
|
if str
|
|
ret.append str
|
|
end
|
|
|
|
replace(ret)
|
|
self
|
|
end
|
|
|
|
def match(pattern, pos=0)
|
|
pattern = Rubinius::Type.coerce_to_regexp(pattern) unless pattern.kind_of? Regexp
|
|
|
|
result = if block_given?
|
|
pattern.match self, pos do |match|
|
|
yield match
|
|
end
|
|
else
|
|
pattern.match self, pos
|
|
end
|
|
Regexp.propagate_last_match
|
|
result
|
|
end
|
|
|
|
# Removes invalid byte sequences from a String, available since Ruby 2.1.
|
|
def scrub(replace = nil)
|
|
output = ''
|
|
input = dup
|
|
|
|
# The default replacement character is the "Unicode replacement" character.
|
|
# (U+FFFD).
|
|
if !replace and !block_given?
|
|
replace = "\xEF\xBF\xBD".force_encoding("UTF-8")
|
|
.encode(self.encoding, :undef => :replace, :replace => '?')
|
|
end
|
|
|
|
if replace
|
|
unless replace.is_a?(String)
|
|
raise(
|
|
TypeError,
|
|
"no implicit conversion of #{replace.class} into String"
|
|
)
|
|
end
|
|
|
|
unless replace.valid_encoding?
|
|
raise(
|
|
ArgumentError,
|
|
"replacement must be a valid byte sequence '#{replace.inspect}'"
|
|
)
|
|
end
|
|
|
|
replace = replace.force_encoding(Encoding::BINARY)
|
|
end
|
|
|
|
# MRI appears to just return a copy of self when the input encoding is
|
|
# BINARY/ASCII_8BIT.
|
|
if input.encoding == Encoding::BINARY
|
|
return input
|
|
end
|
|
|
|
converter = Encoding::Converter.new(input.encoding, Encoding::BINARY)
|
|
|
|
while input.length > 0
|
|
result = converter.primitive_convert(input, output, output.length)
|
|
|
|
if result == :finished
|
|
break
|
|
elsif result == :undefined_conversion
|
|
output << converter.primitive_errinfo[3]
|
|
else
|
|
# Blocks can return strings in any encoding so we'll make sure it's the
|
|
# same as our buffer for the mean time.
|
|
if block_given?
|
|
block_output = yield(converter.primitive_errinfo[3])
|
|
|
|
output << block_output.force_encoding(output.encoding)
|
|
else
|
|
output << replace
|
|
end
|
|
end
|
|
end
|
|
|
|
return output.force_encoding(encoding)
|
|
end
|
|
|
|
def scrub!(replace = nil, &block)
|
|
replace(scrub(replace, &block))
|
|
return self
|
|
end
|
|
|
|
def []=(index, count_or_replacement, replacement=undefined)
|
|
if undefined.equal?(replacement)
|
|
replacement = count_or_replacement
|
|
count = nil
|
|
else
|
|
count = count_or_replacement
|
|
end
|
|
|
|
m = Rubinius::Mirror.reflect self
|
|
|
|
case index
|
|
when Fixnum
|
|
index += size if index < 0
|
|
|
|
if index < 0 or index > size
|
|
raise IndexError, "index #{index} out of string"
|
|
end
|
|
|
|
unless bi = m.byte_index(index)
|
|
raise IndexError, "unable to find character at: #{index}"
|
|
end
|
|
|
|
if count
|
|
count = Rubinius::Type.coerce_to count, Fixnum, :to_int
|
|
|
|
if count < 0
|
|
raise IndexError, "count is negative"
|
|
end
|
|
|
|
total = index + count
|
|
if total >= size
|
|
bs = bytesize - bi
|
|
else
|
|
bs = m.byte_index(total) - bi
|
|
end
|
|
else
|
|
bs = index == size ? 0 : m.byte_index(index + 1) - bi
|
|
end
|
|
|
|
replacement = StringValue replacement
|
|
enc = Rubinius::Type.compatible_encoding self, replacement
|
|
|
|
m.splice bi, bs, replacement
|
|
when String
|
|
unless start = m.byte_index(index)
|
|
raise IndexError, "string not matched"
|
|
end
|
|
|
|
replacement = StringValue replacement
|
|
enc = Rubinius::Type.compatible_encoding self, replacement
|
|
|
|
m.splice start, index.bytesize, replacement
|
|
when Range
|
|
start = Rubinius::Type.coerce_to index.first, Fixnum, :to_int
|
|
|
|
start += size if start < 0
|
|
|
|
if start < 0 or start > size
|
|
raise RangeError, "#{index.first} is out of range"
|
|
end
|
|
|
|
unless bi = m.byte_index(start)
|
|
raise IndexError, "unable to find character at: #{start}"
|
|
end
|
|
|
|
stop = Rubinius::Type.coerce_to index.last, Fixnum, :to_int
|
|
stop += size if stop < 0
|
|
stop -= 1 if index.exclude_end?
|
|
|
|
if stop < start
|
|
bs = 0
|
|
elsif stop >= size
|
|
bs = bytesize - bi
|
|
else
|
|
bs = m.byte_index(stop + 1) - bi
|
|
end
|
|
|
|
replacement = StringValue replacement
|
|
enc = Rubinius::Type.compatible_encoding self, replacement
|
|
|
|
m.splice bi, bs, replacement
|
|
when Regexp
|
|
if count
|
|
count = Rubinius::Type.coerce_to count, Fixnum, :to_int
|
|
else
|
|
count = 0
|
|
end
|
|
|
|
if match = index.match(self)
|
|
ms = match.size
|
|
else
|
|
raise IndexError, "regexp does not match"
|
|
end
|
|
|
|
count += ms if count < 0 and -count < ms
|
|
unless count < ms and count >= 0
|
|
raise IndexError, "index #{count} out of match bounds"
|
|
end
|
|
|
|
unless match[count]
|
|
raise IndexError, "regexp group #{count} not matched"
|
|
end
|
|
|
|
replacement = StringValue replacement
|
|
enc = Rubinius::Type.compatible_encoding self, replacement
|
|
|
|
bi = m.byte_index match.begin(count)
|
|
bs = m.byte_index(match.end(count)) - bi
|
|
|
|
m.splice bi, bs, replacement
|
|
else
|
|
index = Rubinius::Type.coerce_to index, Fixnum, :to_int
|
|
|
|
if count
|
|
return self[index, count] = replacement
|
|
else
|
|
return self[index] = replacement
|
|
end
|
|
end
|
|
|
|
Rubinius::Type.infect self, replacement
|
|
force_encoding enc
|
|
|
|
return replacement
|
|
end
|
|
|
|
def center(width, padding=" ")
|
|
padding = StringValue(padding)
|
|
raise ArgumentError, "zero width padding" if padding.size == 0
|
|
|
|
enc = Rubinius::Type.compatible_encoding self, padding
|
|
|
|
width = Rubinius::Type.coerce_to width, Fixnum, :to_int
|
|
return dup if width <= size
|
|
|
|
width -= size
|
|
left = width / 2
|
|
|
|
bs = bytesize
|
|
pbs = padding.bytesize
|
|
|
|
if pbs > 1
|
|
ps = padding.size
|
|
pm = Rubinius::Mirror.reflect padding
|
|
|
|
x = left / ps
|
|
y = left % ps
|
|
|
|
lpbi = pm.byte_index(y)
|
|
lbytes = x * pbs + lpbi
|
|
|
|
right = left + (width & 0x1)
|
|
|
|
x = right / ps
|
|
y = right % ps
|
|
|
|
rpbi = pm.byte_index(y)
|
|
rbytes = x * pbs + rpbi
|
|
|
|
pad = self.class.pattern rbytes, padding
|
|
str = self.class.pattern lbytes + bs + rbytes, ""
|
|
m = Rubinius::Mirror.reflect str
|
|
|
|
m.copy_from self, 0, bs, lbytes
|
|
m.copy_from pad, 0, lbytes, 0
|
|
m.copy_from pad, 0, rbytes, lbytes + bs
|
|
else
|
|
str = self.class.pattern width + bs, padding
|
|
m = Rubinius::Mirror.reflect str
|
|
m.copy_from self, 0, bs, left
|
|
end
|
|
|
|
Rubinius::Type.infect str, self
|
|
Rubinius::Type.infect str, padding
|
|
|
|
str.force_encoding enc
|
|
end
|
|
|
|
def ljust(width, padding=" ")
|
|
padding = StringValue(padding)
|
|
raise ArgumentError, "zero width padding" if padding.size == 0
|
|
|
|
enc = Rubinius::Type.compatible_encoding self, padding
|
|
|
|
width = Rubinius::Type.coerce_to width, Fixnum, :to_int
|
|
return dup if width <= size
|
|
|
|
width -= size
|
|
|
|
bs = bytesize
|
|
pbs = padding.bytesize
|
|
|
|
if pbs > 1
|
|
ps = padding.size
|
|
pm = Rubinius::Mirror.reflect padding
|
|
|
|
x = width / ps
|
|
y = width % ps
|
|
|
|
pbi = pm.byte_index(y)
|
|
bytes = x * pbs + pbi
|
|
|
|
str = self.class.pattern bytes + bs, self
|
|
m = Rubinius::Mirror.reflect str
|
|
|
|
i = 0
|
|
bi = bs
|
|
|
|
while i < x
|
|
m.copy_from padding, 0, pbs, bi
|
|
|
|
bi += pbs
|
|
i += 1
|
|
end
|
|
|
|
m.copy_from padding, 0, pbi, bi
|
|
else
|
|
str = self.class.pattern width + bs, padding
|
|
m = Rubinius::Mirror.reflect str
|
|
|
|
m.copy_from self, 0, bs, 0
|
|
end
|
|
|
|
Rubinius::Type.infect str, self
|
|
Rubinius::Type.infect str, padding
|
|
|
|
str.force_encoding enc
|
|
end
|
|
|
|
def rjust(width, padding=" ")
|
|
padding = StringValue(padding)
|
|
raise ArgumentError, "zero width padding" if padding.size == 0
|
|
|
|
enc = Rubinius::Type.compatible_encoding self, padding
|
|
|
|
width = Rubinius::Type.coerce_to width, Fixnum, :to_int
|
|
return dup if width <= size
|
|
|
|
width -= size
|
|
|
|
bs = bytesize
|
|
pbs = padding.bytesize
|
|
|
|
if pbs > 1
|
|
ps = padding.size
|
|
pm = Rubinius::Mirror.reflect padding
|
|
|
|
x = width / ps
|
|
y = width % ps
|
|
|
|
bytes = x * pbs + pm.byte_index(y)
|
|
else
|
|
bytes = width
|
|
end
|
|
|
|
str = self.class.pattern bytes + bs, padding
|
|
m = Rubinius::Mirror.reflect str
|
|
|
|
m.copy_from self, 0, bs, bytes
|
|
|
|
Rubinius::Type.infect str, self
|
|
Rubinius::Type.infect str, padding
|
|
|
|
str.force_encoding enc
|
|
end
|
|
|
|
def index(str, start=undefined)
|
|
if undefined.equal?(start)
|
|
start = 0
|
|
else
|
|
start = Rubinius::Type.coerce_to start, Fixnum, :to_int
|
|
|
|
start += size if start < 0
|
|
return if start < 0 or start > size
|
|
end
|
|
|
|
if str.kind_of? Regexp
|
|
Rubinius::Type.compatible_encoding self, str
|
|
|
|
m = Rubinius::Mirror.reflect self
|
|
start = m.character_to_byte_index start
|
|
if match = str.match_from(self, start)
|
|
Regexp.last_match = match
|
|
return match.begin(0)
|
|
else
|
|
Regexp.last_match = nil
|
|
return
|
|
end
|
|
end
|
|
|
|
str = StringValue(str)
|
|
return start if str == ""
|
|
|
|
Rubinius::Type.compatible_encoding self, str
|
|
|
|
return if str.size > size
|
|
|
|
m = Rubinius::Mirror.reflect self
|
|
m.character_index str, start
|
|
end
|
|
|
|
def rindex(sub, finish=undefined)
|
|
if undefined.equal?(finish)
|
|
finish = size
|
|
else
|
|
finish = Rubinius::Type.coerce_to(finish, Integer, :to_int)
|
|
finish += size if finish < 0
|
|
return nil if finish < 0
|
|
finish = size if finish >= size
|
|
end
|
|
|
|
m = Rubinius::Mirror.reflect self
|
|
byte_finish = m.character_to_byte_index finish
|
|
|
|
case sub
|
|
when Fixnum
|
|
if finish == size
|
|
return nil if finish == 0
|
|
finish -= 1
|
|
end
|
|
|
|
begin
|
|
str = sub.chr
|
|
rescue RangeError
|
|
return nil
|
|
end
|
|
|
|
if byte_index = m.find_string_reverse(str, byte_finish)
|
|
return m.byte_to_character_index byte_index
|
|
end
|
|
|
|
when Regexp
|
|
Rubinius::Type.compatible_encoding self, sub
|
|
|
|
match_data = sub.search_region(self, 0, byte_finish, false)
|
|
Regexp.last_match = match_data
|
|
return match_data.begin(0) if match_data
|
|
|
|
else
|
|
needle = StringValue(sub)
|
|
needle_size = needle.size
|
|
|
|
# needle is bigger that haystack
|
|
return nil if size < needle_size
|
|
|
|
# Boundary case
|
|
return finish if needle_size == 0
|
|
|
|
Rubinius::Type.compatible_encoding self, needle
|
|
|
|
if byte_index = m.find_string_reverse(needle, byte_finish)
|
|
return m.byte_to_character_index byte_index
|
|
end
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
def start_with?(*prefixes)
|
|
prefixes.each do |original_prefix|
|
|
prefix = Rubinius::Type.check_convert_type original_prefix, String, :to_str
|
|
unless prefix
|
|
raise TypeError, "no implicit conversion of #{original_prefix.class} into String"
|
|
end
|
|
return true if self[0, prefix.length] == prefix
|
|
end
|
|
false
|
|
end
|
|
|
|
def insert(index, other)
|
|
other = StringValue(other)
|
|
|
|
enc = Rubinius::Type.compatible_encoding self, other
|
|
index = Rubinius::Type.coerce_to index, Fixnum, :to_int
|
|
index = length + 1 + index if index < 0
|
|
|
|
if index > length or index < 0
|
|
raise IndexError, "index #{index} out of string"
|
|
end
|
|
|
|
osize = other.bytesize
|
|
size = @num_bytes + osize
|
|
str = self.class.pattern size, "\0"
|
|
|
|
self_m = Rubinius::Mirror.reflect self
|
|
index = self_m.character_to_byte_index index
|
|
|
|
Rubinius.check_frozen
|
|
@hash_value = nil
|
|
|
|
m = Rubinius::Mirror.reflect str
|
|
if index == @num_bytes
|
|
m.copy_from self, 0, @num_bytes, 0
|
|
m.copy_from other, 0, osize, @num_bytes
|
|
else
|
|
m.copy_from self, 0, index, 0 if index > 0
|
|
m.copy_from other, 0, osize, index
|
|
m.copy_from self, index, @num_bytes - index, index + osize
|
|
end
|
|
|
|
self.num_bytes = size
|
|
@data = str.__data__
|
|
Rubinius::Type.infect self, other
|
|
force_encoding enc
|
|
|
|
self
|
|
end
|
|
|
|
def tr_trans(source, replacement, squeeze)
|
|
source = StringValue(source).dup
|
|
replacement = StringValue(replacement).dup
|
|
|
|
return delete!(source) if replacement.empty?
|
|
return if @num_bytes == 0
|
|
|
|
invert = source[0] == ?^ && source.length > 1
|
|
|
|
source.slice!(0) if invert
|
|
source.tr_expand! nil, true
|
|
replacement.tr_expand! nil, false
|
|
|
|
multi_table = {}
|
|
|
|
if invert
|
|
r = replacement.__data__[replacement.size - 1]
|
|
table = Rubinius::Tuple.pattern 256, r
|
|
|
|
source.each_char do |chr|
|
|
if chr.bytesize > 1
|
|
multi_table[chr] = -1
|
|
else
|
|
table[chr.ord] = -1
|
|
end
|
|
end
|
|
else
|
|
repl = replacement.__data__
|
|
rsize = replacement.size
|
|
table = Rubinius::Tuple.pattern 256, -1
|
|
|
|
i = 0
|
|
source.each_char do |chr|
|
|
repl_char = replacement[i]
|
|
|
|
if repl_char && (chr.bytesize > 1 || repl_char.bytesize > 1)
|
|
multi_table[chr] = repl_char
|
|
else
|
|
r = repl[i] if i < rsize
|
|
table[chr.ord] = r
|
|
end
|
|
|
|
i += 1
|
|
end
|
|
end
|
|
|
|
destination = dup
|
|
modified = false
|
|
|
|
if squeeze
|
|
last = nil
|
|
byte_size = 0
|
|
|
|
i = 0
|
|
each_char do |chr|
|
|
c = -1
|
|
c = table[chr.ord] if chr.bytesize == 1
|
|
|
|
if c >= 0
|
|
c_char = c.chr
|
|
next if last == c_char
|
|
byte_size += 1
|
|
destination[i] = c_char
|
|
last = c_char
|
|
modified = true
|
|
elsif c = multi_table[chr]
|
|
next if last == c
|
|
destination[i] = c
|
|
last = c
|
|
modified = true
|
|
byte_size += c.bytesize
|
|
else
|
|
destination[i] = chr
|
|
byte_size += chr.bytesize
|
|
last = nil
|
|
end
|
|
|
|
i += 1
|
|
end
|
|
|
|
destination.num_bytes = byte_size if byte_size < @num_bytes
|
|
else
|
|
i = 0
|
|
each_char do |chr|
|
|
c = -1
|
|
c = table[chr.ord] if chr.bytesize == 1
|
|
|
|
if c >= 0
|
|
c_char = c.chr
|
|
destination[i] = c_char
|
|
modified = true
|
|
elsif c = multi_table[chr]
|
|
destination[i] = c
|
|
modified = true
|
|
end
|
|
i += 1
|
|
end
|
|
end
|
|
|
|
if modified
|
|
replace(destination)
|
|
else
|
|
nil
|
|
end
|
|
end
|
|
|
|
def <=>(other)
|
|
if other.kind_of?(String)
|
|
result = @data.compare_bytes(other.__data__, @num_bytes, other.bytesize)
|
|
|
|
if result == 0
|
|
if Encoding.compatible?(self, other)
|
|
0
|
|
else
|
|
Rubinius::Type.encoding_order(encoding, other.encoding)
|
|
end
|
|
else
|
|
result
|
|
end
|
|
else
|
|
if other.respond_to?(:<=>) && !other.respond_to?(:to_str)
|
|
return unless tmp = (other <=> self)
|
|
elsif other.respond_to?(:to_str)
|
|
return unless tmp = (other.to_str <=> self)
|
|
else
|
|
return
|
|
end
|
|
return -tmp # We're not supposed to convert to integer here
|
|
end
|
|
end
|
|
|
|
def dump
|
|
s = self.class.allocate
|
|
str = %{"#{transform(Rubinius::CType::Printed).force_encoding(Encoding::US_ASCII)}"}
|
|
str += ".force_encoding(\"#{encoding}\")" unless encoding.ascii_compatible?
|
|
s.replace(str)
|
|
end
|
|
|
|
def -@
|
|
frozen? ? self : dup.freeze
|
|
end
|
|
|
|
def +@
|
|
frozen? ? dup : self
|
|
end
|
|
end
|