rubinius/rubinius

View on GitHub
core/string.rb

Summary

Maintainability
F
1 mo
Test Coverage
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!(/[&><]/, '&' => '&amp;', '>' => '&gt;', '<' => '&lt;')
      when :attr
        gsub!(/[&><"]/, '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;')
        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