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 self as a string of hexadecimal digits # (with an optional sign and an optional 0x) 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