[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/mruby/mruby/master/src/string.c [Back]  [Original]

/*
** string.c - String class
**
** See Copyright Notice in mruby.h
*/

#ifdef _MSC_VER
# define _CRT_NONSTDC_NO_DEPRECATE
# define WIN32_LEAN_AND_MEAN
#endif

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

typedef struct mrb_shared_string {
  int refcnt;
  mrb_int capa;
  /* Offset past the last byte any sharer can see. Bytes at or above it are
     dead to every sharer, so a writer may use them in place (str_modify_cat).
     Only grows, as sharers are added. */
  mrb_int reserved;
  char *ptr;
} mrb_shared_string;

const char mrb_digitmap[] = "0123456789abcdefghijklmnopqrstuvwxyz";

#define mrb_obj_alloc_string(mrb) MRB_OBJ_ALLOC((mrb), MRB_TT_STRING, (mrb)->string_class)

#ifndef MRB_STR_LENGTH_MAX
#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__)
#define MRB_STR_LENGTH_MAX 0
#else
#define MRB_STR_LENGTH_MAX 1048576
#endif
#endif

static void
str_check_length(mrb_state *mrb, mrb_int len)
{
  if (len < 0 || len == MRB_INT_MAX) {
    mrb_raise(mrb, E_ARGUMENT_ERROR, "negative (or overflowed) string size");
  }
#if MRB_STR_LENGTH_MAX != 0
  if (len > MRB_STR_LENGTH_MAX-1) {
    mrb_raisef(mrb, E_ARGUMENT_ERROR, "string too long (len=%i max=" MRB_STRINGIZE(MRB_STR_LENGTH_MAX) ")", len);
  }
#endif
}

mrb_bool
mrb_strcasecmp_p(const char *s1, mrb_int len1, const char *s2, mrb_int len2)
{
  if (len1 != len2) return FALSE;

  const char *e1 = s1 + len1;
  while (s1 < e1) {
    if (*s1 != *s2 && TOUPPER(*s1) != TOUPPER(*s2)) return FALSE;
    s1++;
    s2++;
  }
  return TRUE;
}

static struct RString*
str_init_normal_capa(mrb_state *mrb, struct RString *s,
                     const char *p, mrb_int len, mrb_int capa)
{
  str_check_length(mrb, capa);
  char *dst = (char*)mrb_malloc(mrb, capa + 1);
  if (p) memcpy(dst, p, len);
  dst[len] = '\0';
  s->as.heap.ptr = dst;
  s->as.heap.len = len;
  s->as.heap.aux.capa = capa;
  RSTR_SET_TYPE(s, NORMAL);
  return s;
}

static struct RString*
str_init_normal(mrb_state *mrb, struct RString *s, const char *p, mrb_int len)
{
  return str_init_normal_capa(mrb, s, p, len, len);
}

static struct RString*
str_init_embed(struct RString *s, const char *p, mrb_int len)
{
  mrb_assert(len >= 0);
  if (p) memcpy(RSTR_EMBED_PTR(s), p, len);
  RSTR_EMBED_PTR(s)[len] = '\0';
  RSTR_SET_TYPE(s, EMBED);
  RSTR_SET_EMBED_LEN(s, len);
  return s;
}

static struct RString*
str_init_nofree(struct RString *s, const char *p, mrb_int len)
{
  s->as.heap.ptr = (char*)p;
  s->as.heap.len = len;
  s->as.heap.aux.capa = 0;             /* nofree */
  RSTR_SET_TYPE(s, NOFREE);
  return s;
}

static struct RString*
str_init_shared(mrb_state *mrb, const struct RString *orig, struct RString *s, mrb_shared_string *shared)
{
  if (shared) {
    mrb_int end = (mrb_int)(orig->as.heap.ptr - shared->ptr) + orig->as.heap.len;
    if (shared->reserved < end) shared->reserved = end;
    shared->refcnt++;
  }
  else {
    shared = (mrb_shared_string*)mrb_malloc(mrb, sizeof(mrb_shared_string));
    shared->refcnt = 1;
    shared->ptr = orig->as.heap.ptr;
    shared->capa = orig->as.heap.aux.capa;
    shared->reserved = orig->as.heap.len;
  }
  s->as.heap.ptr = orig->as.heap.ptr;
  s->as.heap.len = orig->as.heap.len;
  s->as.heap.aux.shared = shared;
  RSTR_SET_TYPE(s, SHARED);
  return s;
}

static struct RString*
str_init_fshared(const struct RString *orig, struct RString *s, struct RString *fshared)
{
  s->as.heap.ptr = orig->as.heap.ptr;
  s->as.heap.len = orig->as.heap.len;
  s->as.heap.aux.fshared = fshared;
  RSTR_SET_TYPE(s, FSHARED);
  return s;
}

static struct RString*
str_init_modifiable(mrb_state *mrb, struct RString *s, const char *p, mrb_int len)
{
  if (RSTR_EMBEDDABLE_P(len)) {
    return str_init_embed(s, p, len);
  }
  return str_init_normal(mrb, s, p, len);
}

static struct RString*
str_new_static(mrb_state *mrb, const char *p, mrb_int len)
{
  if (RSTR_EMBEDDABLE_P(len)) {
    return str_init_embed(mrb_obj_alloc_string(mrb), p, len);
  }
  return str_init_nofree(mrb_obj_alloc_string(mrb), p, len);
}

static struct RString*
str_new(mrb_state *mrb, const char *p, mrb_int len)
{
  str_check_length(mrb, len);
  if (RSTR_EMBEDDABLE_P(len)) {
    return str_init_embed(mrb_obj_alloc_string(mrb), p, len);
  }
  if (p && mrb_ro_data_p(p)) {
    return str_init_nofree(mrb_obj_alloc_string(mrb), p, len);
  }
  return str_init_normal(mrb, mrb_obj_alloc_string(mrb), p, len);
}

/*
 * @param mrb The mruby state.
 * @param capa The desired capacity of the new string.
 * @return A new mruby string with the specified capacity.
 *
 * Creates a new mruby string with a given initial capacity.
 * The string is initially empty.
 */
MRB_API mrb_value
mrb_str_new_capa(mrb_state *mrb, mrb_int capa)
{
  struct RString *s = mrb_obj_alloc_string(mrb);

  if (RSTR_EMBEDDABLE_P(capa)) {
    s = str_init_embed(s, NULL, 0);
  }
  else {
    s = str_init_normal_capa(mrb, s, NULL, 0, capa);
  }
  return mrb_obj_value(s);
}

static void
resize_capa(mrb_state *mrb, struct RString *s, mrb_int capacity)
{
  if (RSTR_EMBED_P(s)) {
    if (!RSTR_EMBEDDABLE_P(capacity)) {
      str_init_normal_capa(mrb, s, RSTR_EMBED_PTR(s), RSTR_EMBED_LEN(s), capacity);
    }
  }
  else {
    str_check_length(mrb, capacity);
    s->as.heap.ptr = (char*)mrb_realloc(mrb, RSTR_PTR(s), capacity+1);
    s->as.heap.aux.capa = (mrb_ssize)capacity;
  }
}

/*
 * @param mrb The mruby state.
 * @param p A pointer to the C string to copy.
 * @param len The length of the C string.
 * @return A new mruby string containing the copied C string.
 *
 * Creates a new mruby string from a C string and a specified length.
 * If `p` is NULL, an empty string is created.
 */
MRB_API mrb_value
mrb_str_new(mrb_state *mrb, const char *p, mrb_int len)
{
  return mrb_obj_value(str_new(mrb, p, len));
}

/*
 * @param mrb The mruby state.
 * @param p A pointer to the null-terminated C string to copy.
 * @return A new mruby string containing the copied C string.
 *
 * Creates a new mruby string from a null-terminated C string.
 * If `p` is NULL, an empty string is created.
 */
MRB_API mrb_value
mrb_str_new_cstr(mrb_state *mrb, const char *p)
{
  struct RString *s;
  mrb_int len;

  if (p) {
    len = strlen(p);
  }
  else {
    len = 0;
  }

  s = str_new(mrb, p, len);

  return mrb_obj_value(s);
}

/*
 * @param mrb The mruby state.
 * @param p A pointer to the static C string.
 * @param len The length of the static C string.
 * @return A new mruby string referencing the static C string.
 *
 * Creates a new mruby string that directly references a static C string.
 * The C string is not copied and must remain valid for the lifetime of the mruby string.
 * This is typically used for string literals.
 */
MRB_API mrb_value
mrb_str_new_static(mrb_state *mrb, const char *p, mrb_int len)
{
  struct RString *s = str_new_static(mrb, p, len);
  return mrb_obj_value(s);
}

static void
str_decref(mrb_state *mrb, mrb_shared_string *shared)
{
  shared->refcnt--;
  if (shared->refcnt == 0) {
    mrb_free(mrb, shared->ptr);
    mrb_free(mrb, shared);
  }
}

static void
str_unshare_buffer(mrb_state *mrb, struct RString *s)
{
  if (RSTR_SHARED_P(s)) {
    mrb_shared_string *shared = s->as.heap.aux.shared;

    if (shared->refcnt == 1 && s->as.heap.ptr == shared->ptr) {
      s->as.heap.aux.capa = shared->capa;
      s->as.heap.ptr[s->as.heap.len] = '\0';
      RSTR_SET_TYPE(s, NORMAL);
      mrb_free(mrb, shared);
    }
    else {
      str_init_modifiable(mrb, s, s->as.heap.ptr, s->as.heap.len);
      str_decref(mrb, shared);
    }
  }
  else if (RSTR_NOFREE_P(s) || RSTR_FSHARED_P(s)) {
    str_init_modifiable(mrb, s, s->as.heap.ptr, s->as.heap.len);
  }
}

static void
check_null_byte(mrb_state *mrb, struct RString *str)
{
  const char *p = RSTR_PTR(str);
  if (p && memchr(p, '\0', RSTR_LEN(str))) {
    mrb_raise(mrb, E_ARGUMENT_ERROR, "string contains null byte");
  }
}

void
mrb_gc_free_str(mrb_state *mrb, struct RString *str)
{
  if (RSTR_EMBED_P(str))
    /* no code */;
  else if (RSTR_SHARED_P(str))
    str_decref(mrb, str->as.heap.aux.shared);
  else if (!RSTR_NOFREE_P(str) && !RSTR_FSHARED_P(str))
    mrb_free(mrb, str->as.heap.ptr);
}

#if defined(__i386) || defined(__i386__) || defined(_M_IX86) || \
     defined(__x86_64) || defined(__x86_64__) || defined(_M_AMD64) || \
     defined(__powerpc64__) || defined(__POWERPC__) || defined(__aarch64__) || \
     defined(__mc68020__)
# define ALIGNED_WORD_ACCESS 0
#else
# define ALIGNED_WORD_ACCESS 1
#endif

#ifdef MRB_64BIT
#define bitint uint64_t
#define MASK01 0x0101010101010101ull
#else
#define bitint uint32_t
#define MASK01 0x01010101ul
#endif

/* Encode a Unicode codepoint to UTF-8 bytes, into a buffer of at least four.
   Returns the number of bytes written (1-4), or 0 for a value outside
   U+0000..U+10FFFF, which spells no character. The value arrives as an
   mrb_int so that a negative one and one past the range are both this
   function's answer to give; a caller reporting them differs only in which
   exception it raises, and each raises what CRuby raises there.

   A surrogate does encode. What CRuby writes for one is what mruby writes:
   sprintf("%c", 0xD800) and [0xD800].pack("U") both yield ED A0 80 there.
   Reading those bytes back is a separate question, and mrb_utf8len() answers
   it by RFC 3629, under which a surrogate spells nothing. So what this writes
   is deliberately wider than what that reads, and a string built from one is
   valid_encoding? == false. */
mrb_int
mrb_utf8_to_buf(char *buf, mrb_int cp)
{
  if (cp < 0) {
    return 0;
  }
  else if (cp < 0x80) {
    buf[0] = (char)cp;
    return 1;
  }
  else if (cp < 0x800) {
    buf[0] = (char)(0xC0 | (cp >> 6));
    buf[1] = (char)(0x80 | (cp & 0x3F));
    return 2;
  }
  else if (cp < 0x10000) {
    buf[0] = (char)(0xE0 | (cp >> 12));
    buf[1] = (char)(0x80 | ((cp >> 6) & 0x3F));
    buf[2] = (char)(0x80 | (cp & 0x3F));
    return 3;
  }
  else if (cp > 18));
    buf[1] = (char)(0x80 | ((cp >> 12) & 0x3F));
    buf[2] = (char)(0x80 | ((cp >> 6) & 0x3F));
    buf[3] = (char)(0x80 | (cp & 0x3F));
    return 4;
  }
  return 0;  /* above U+10FFFF */
}

/* UTF-8: what a run of bytes spells, and what a string holds character by
   character. Only a build that indexes strings by character has to answer
   either, so a build without MRB_UTF8_STRING carries none of it. */
#ifdef MRB_UTF8_STRING

#define utf8_islead(c) ((unsigned char)((c)&0xc0) != 0x80)

/* the byte length a lead byte claims, read only through mrb_utf8len() */
static const char mrb_utf8len_table[] = {
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 3, 3, 4, 0
};

mrb_int
mrb_utf8len(const char* p, const char* e)
{
  mrb_int len = mrb_utf8len_table[(unsigned char)p[0] >> 3];
  if (len > e - p) return 1;
  switch (len) {
  case 0:
    return 1;
  case 4:
    if (utf8_islead(p[3])) return 1;
  case 3:
    if (utf8_islead(p[2])) return 1;
  case 2:
    if (utf8_islead(p[1])) return 1;
  }
  /* Reject overlong sequences, UTF-16 surrogates, and code points above
     U+10FFFF (RFC 3629, Unicode D93b). */
  switch ((unsigned char)p[0]) {
  case 0xC0: case 0xC1:                       /* overlong (< U+0080) */
    return 1;
  case 0xE0:                                  /* overlong (< U+0800) */
    if ((unsigned char)p[1] < 0xA0) return 1;
    break;
  case 0xED:                                  /* surrogate (U+D800..U+DFFF) */
    if ((unsigned char)p[1] > 0x9F) return 1;
    break;
  case 0xF0:                                  /* overlong (< U+10000) */
    if ((unsigned char)p[1] < 0x90) return 1;
    break;
  case 0xF4:                                  /* above U+10FFFF */
    if ((unsigned char)p[1] > 0x8F) return 1;
    break;
  case 0xF5: case 0xF6: case 0xF7:            /* above U+10FFFF */
    return 1;
  }
  return len;
}

/* The byte the character covering `p` starts at, or `p` itself when `p` is
   already a character boundary. A continuation byte belongs to the character
   that reaches it; one that no lead byte reaches belongs to none and stands as
   a character of its own. Whether a lead byte reaches is mrb_utf8len()'s
   answer, so the boundaries found here are the ones the character count is
   taken over. Reading back three bytes covers it, since nothing longer than
   four bytes spells a character. */
const char*
mrb_utf8_char_head(const char *beg, const char *p, const char *end)
{
  if (p >= end || utf8_islead(p[0])) return p;
  for (mrb_int back = 1; back > POPC_SHIFT);
}
#endif

/* Counts characters, and when `validp` is given also reports whether every
   sequence decoded as one character. The walk stops at the first broken
   sequence, so the returned count is a character count only while `*validp`
   stays TRUE. */
static mrb_int
utf8_strlen_check(const char *str, mrb_int byte_len, mrb_bool *validp)
{
  const char *p = str;
  const char *e = str + byte_len;
  mrb_int len = 0;

  while (p < e) {
    const char *np = search_nonascii(p, e);

    len += np - p;
    if (np == e) break;
    p = np;
    while (p < e && NOASCII(*p)) {
      mrb_int clen = mrb_utf8len(p, e);

      /* mrb_utf8len() answers 1 for a byte that leads no valid sequence. The
         byte here is known to be non-ASCII, so a length of 1 means the string
         carries a byte that stands for no character. */
      if (validp && clen == 1) {
        *validp = FALSE;
        return len;
      }
      p += clen;
      len++;
    }
  }
  return len;
}

mrb_int
mrb_utf8_strlen(const char *str, mrb_int byte_len)
{
  return utf8_strlen_check(str, byte_len, NULL);
}

/* count the characters of a string */
mrb_int
mrb_str_char_len(mrb_state *mrb, mrb_value str)
{
  (void)mrb;
  struct RString *s = mrb_str_ptr(str);
  mrb_int byte_len = RSTR_LEN(s);

  /* A single-byte string has one position per byte, which is what
     mrb_str_char_to_byte() and mrb_str_byte_to_char() already answer for it.
     Asked here only where the string stands, the same string was measured as
     UTF-8 and reported a length its own indexing did not agree with.

     Nothing is recorded on the way out. A string of nothing but ASCII carries
     that already, and a byte-read one returns here because of how it is read
     rather than because of what its bytes are: 7BIT would be a claim about
     bytes nothing has looked at, and force_encoding() can take the byte
     reading away again and leave the claim standing. */
  if (RSTR_SINGLE_BYTE_P(s)) {
    return byte_len;
  }
  else {
    const char *p = RSTR_PTR(s);
    const char *e = p + byte_len;
    const char *np = search_nonascii(p, e);

    /* Every character a non-ASCII byte begins spells two bytes or more, and a
       non-ASCII byte that begins none spells no character at all, so a string
       holds one character per byte exactly when every byte of it is ASCII.
       Counts that come out equal do not say that: a byte spelling no character
       is counted as one too, so a string of them set the flag as well, and the
       readers of it went on to hand those bytes back as characters. */
    if (np == e) {
      RSTR_CODERANGE_SET(s, MRB_STR_CODERANGE_7BIT);
      return byte_len;
    }
    mrb_int utf8_len = (mrb_int)(np - p) + mrb_utf8_strlen(np, (mrb_int)(e - np));
    mrb_assert(utf8_len  256) {
      resize_capa(mrb, s, len);
    }
    RSTR_SET_LEN(s, len);
    RSTR_PTR(s)[len] = '\0';   /* sentinel */
  }
  return str;
}

/*
 * @param mrb The mruby state.
 * @param str0 The mruby string to convert.
 * @return A pointer to a null-terminated C string.
 *
 * Converts an mruby string to a null-terminated C string.
 * This function may allocate a new C string if the mruby string
 * contains null bytes or is not already null-terminated.
 * The caller is responsible for managing the memory of the returned C string
 * if it's different from the string's internal buffer.
 * Raises E_ARGUMENT_ERROR if the string contains a null byte.
 * Note: This function creates a *new* RString object to hold the C-string version if modification is needed.
 * It's generally recommended to use RSTRING_PTR and RSTRING_LEN for direct access
 * and ensure null termination manually if needed, or use mrb_string_cstr for a (potentially new) null-terminated string.
 */
MRB_API char*
mrb_str_to_cstr(mrb_state *mrb, mrb_value str0)
{
  struct RString *s;

  const char *p = RSTRING_PTR(str0);
  mrb_int len = RSTRING_LEN(str0);
  check_null_byte(mrb, RSTRING(str0));
  s = str_init_modifiable(mrb, mrb_obj_alloc_string(mrb), p, len);
  return RSTR_PTR(s);
}

/*
 * @param mrb The mruby state.
 * @param self The mruby string to append to (modified in place).
 * @param other The mruby value to append (will be converted to a string).
 *
 * Concatenates the string representation of `other` to `self`.
 * `self` is modified in place.
 */
MRB_API void
mrb_str_concat(mrb_state *mrb, mrb_value self, mrb_value other)
{
  other = mrb_obj_as_string(mrb, other);
  mrb_str_cat_str(mrb, self, other);
}

/*
 * @param mrb The mruby state.
 * @param a The first mruby string.
 * @param b The second mruby string.
 * @return A new mruby string that is the concatenation of `a` and `b`.
 *
 * Creates a new mruby string by concatenating two existing mruby strings.
 */
MRB_API mrb_value
mrb_str_plus(mrb_state *mrb, mrb_value a, mrb_value b)
{
  struct RString *s = mrb_str_ptr(a);
  struct RString *s2 = mrb_str_ptr(b);
  struct RString *t;
  mrb_int slen = RSTR_LEN(s);
  mrb_int s2len = RSTR_LEN(s2);
  const char *p = RSTR_PTR(s);
  const char *p2 = RSTR_PTR(s2);

  t = str_new(mrb, 0, slen + s2len);
  char *pt = RSTR_PTR(t);
  memcpy(pt, p, slen);
  memcpy(pt + slen, p2, s2len);

  /* The sum is a string with no history, so its reading comes from the bytes
     it was built out of rather than from either operand's standing. Two
     byte-read operands stay that way, and one byte-read operand carrying a
     byte above ASCII hands the sum bytes no other reading holds, so its
     reading wins. A byte-read operand of ASCII bytes carries no such evidence
     and yields to the other operand.

     Two places this does not answer as CRuby does, both on purpose:

     - `"abc".b + "def"` is UTF-8 here and ASCII-8BIT there. Where both
       operands are entirely ASCII, CRuby keeps the receiver's encoding; this
       rule is symmetric in the operands, because the one bit it tracks says
       "bytes read as bytes landed here" and ASCII bytes never say that.
       `mrb_str_cat_str()` answers ASCII-8BIT for the same pair, and the two
       part company on purpose: appending changes a string that was already
       being read some way, while `+` builds one that was not being read at
       all. Following CRuby on `+` alone would put it at odds with `join`,
       and following it there too would mean taking the byte reading back off
       a string that carries it, which nothing here does.
     - The pairs CRuby refuses outright with Encoding::CompatibilityError come
       out byte-read, saying nothing rather than something false. mruby has no
       such exception. */
  if ((RSTR_BINARY_P(s) && RSTR_BINARY_P(s2)) ||
      (RSTR_BINARY_P(s) && !str_ascii_p(s)) ||
      (RSTR_BINARY_P(s2) && !str_ascii_p(s2))) {
    RSTR_ENCODING_SET(t, MRB_STR_ENCODING_BINARY);
  }

  return mrb_obj_value(t);
}

/* 15.2.10.5.2  */

/*
 *  call-seq:
 *     str + other_str   -> new_str
 *
 *  Concatenation---Returns a new `String` containing
 *  `other_str` concatenated to `str`.
 *
 *     "Hello from " + self.to_s   #=> "Hello from main"
 */
static mrb_value
mrb_str_plus_m(mrb_state *mrb, mrb_value self)
{
  mrb_value str;

  mrb_get_args(mrb, "S", &str);
  return mrb_str_plus(mrb, self, str);
}

/* 15.2.10.5.26 */
/* 15.2.10.5.33 */
/*
 *  call-seq:
 *     "abcd".size   => int
 *
 *  Returns the length of string.
 */
static mrb_value
mrb_str_size(mrb_state *mrb, mrb_value self)
{
  mrb_int len = mrb_str_char_len(mrb, self);
  return mrb_int_value(mrb, len);
}

static mrb_value
mrb_str_bytesize(mrb_state *mrb, mrb_value self)
{
  return mrb_int_value(mrb, RSTRING_LEN(self));
}

/* 15.2.10.5.1  */
/*
 *  call-seq:
 *     str * integer   => new_str
 *
 *  Copy---Returns a new `String` containing `integer` copies of
 *  the receiver.
 *
 *     "Ho! " * 3   #=> "Ho! Ho! Ho! "
 */
static mrb_value
mrb_str_times(mrb_state *mrb, mrb_value self)
{
  mrb_int len, times;

  mrb_get_args(mrb, "i", ×);
  if (times < 0) {
    mrb_raise(mrb, E_ARGUMENT_ERROR, "negative argument");
  }
  if (mrb_int_mul_overflow(RSTRING_LEN(self), times, &len)) {
    mrb_raise(mrb, E_ARGUMENT_ERROR, "argument too big");
  }

  struct RString *str2 = str_new(mrb, 0, len);
  char *p = RSTR_PTR(str2);
  if (len > 0) {
    mrb_int n = RSTRING_LEN(self);
    memcpy(p, RSTRING_PTR(self), n);
    while (n  int or nil
 *     str[int, int]            => new_str or nil
 *     str[range]               => new_str or nil
 *     str[other_str]           => new_str or nil
 *     str.slice(int)           => int or nil
 *     str.slice(int, int)      => new_str or nil
 *     str.slice(range)         => new_str or nil
 *     str.slice(other_str)     => new_str or nil
 *
 *  Element Reference---If passed a single `Integer`, returns the code
 *  of the character at that position. If passed two `Integer`
 *  objects, returns a substring starting at the offset given by the first, and
 *  a length given by the second. If given a range, a substring containing
 *  characters at offsets given by the range is returned. In all three cases, if
 *  an offset is negative, it is counted from the end of *str*. Returns
 *  `nil` if the initial offset falls outside the string, the length
 *  is negative, or the beginning of the range is greater than the end.
 *
 *  If a `String` is given, that string is returned if it occurs in
 *  *str*. In both cases, `nil` is returned if there is no
 *  match.
 *
 *     a = "hello there"
 *     a[1]                   #=> 101(1.8.7) "e"(1.9.2)
 *     a[1.1]                 #=>            "e"(1.9.2)
 *     a[1,3]                 #=> "ell"
 *     a[1..3]                #=> "ell"
 *     a[-3,2]                #=> "er"
 *     a[-4..-2]              #=> "her"
 *     a[12..-1]              #=> nil
 *     a[-2..-4]              #=> ""
 *     a["lo"]                #=> "lo"
 *     a["bye"]               #=> nil
 */
static mrb_value
mrb_str_aref_m(mrb_state *mrb, mrb_value str)
{
  mrb_value a1, a2;

  if (mrb_get_args(mrb, "o|o", &a1, &a2) == 1) {
    a2 = mrb_undef_value();
  }

  return mrb_str_aref(mrb, str, a1, a2);
}

static mrb_noreturn void
str_out_of_index(mrb_state *mrb, mrb_value index)
{
  mrb_raisef(mrb, E_INDEX_ERROR, "index %v out of string", index);
}

/* Bytes spliced in mark the string they land in the way appended ones do:
   byte-read bytes above ASCII spell no character here and hand their reading
   over, ASCII bytes move nothing. */
static void
str_mark_spliced_binary(struct RString *str, struct RString *rep)
{
  if (!RSTR_BINARY_P(str) && RSTR_BINARY_P(rep) && !str_ascii_p(rep)) {
    RSTR_ENCODING_SET(str, MRB_STR_ENCODING_BINARY);
  }
}

static mrb_value
str_replace_partial(mrb_state *mrb, mrb_value src, mrb_int pos, mrb_int end, mrb_value rep)
{
  const mrb_int shrink_threshold = 256;
  struct RString *str = mrb_str_ptr(src);
  mrb_int len = RSTR_LEN(str);
  mrb_int replen, newlen;
  char *strp;

  if (end > len) { end = len; }

  if (pos < 0 || pos > len) {
    str_out_of_index(mrb, mrb_int_value(mrb, pos));
  }

  replen = (mrb_nil_p(rep) ? 0 : RSTRING_LEN(rep));
  if (mrb_int_add_overflow(replen, len - (end - pos), &newlen)) {
    mrb_raise(mrb, E_RUNTIME_ERROR, "string size too big");
  }

  /* Replacing the empty range at the end is an append: it writes nothing any
     sharer of the buffer can see, so mrb_str_cat() may grow the string inside
     that buffer where mrb_str_modify() below would copy the whole of it first.
     mrb_str_cat() checks the frozen receiver on every length, so the check
     mrb_str_modify() would have made is not lost. */
  if (pos == end && end == len && !mrb_nil_p(rep)) {
    mrb_str_cat(mrb, src, RSTRING_PTR(rep), (size_t)replen);
    str_mark_spliced_binary(str, mrb_str_ptr(rep));
    return src;
  }

  mrb_str_modify(mrb, str);

  if (len < newlen) {
    resize_capa(mrb, str, newlen);
  }

  strp = RSTR_PTR(str);

  memmove(strp + newlen - (len - end), strp + end, len - end);
  if (!mrb_nil_p(rep)) {
    memmove(strp + pos, RSTRING_PTR(rep), replen);
    str_mark_spliced_binary(str, mrb_str_ptr(rep));
  }
  RSTR_SET_LEN(str, newlen);
  strp[newlen] = '\0';

  if (len - newlen >= shrink_threshold) {
    resize_capa(mrb, str, newlen);
  }

  return src;
}

#define IS_EVSTR(p,e) ((p) < (e) && (*(p) == '$' || *(p) == '@' || *(p) == '{'))

/* A `\xNN` escape spells its byte in upper case, as CRuby writes it.
   `mrb_digitmap` is lower case because `Integer#to_s` reads a number
   through it and CRuby spells that in lower case, so the two cannot share
   one table. */
static const char escape_hexmap[] = "0123456789ABCDEF";

static mrb_value
str_escape(mrb_state *mrb, mrb_value str, mrb_bool inspect)
{
  const char *p, *pend;
  char buf[4];  /* `\x??` or UTF-8 character */
  mrb_value result = mrb_str_new_lit(mrb, "\"");
#ifdef MRB_UTF8_STRING
  mrb_bool sb_flag = TRUE;      /* whether `result` comes out single byte */
  mrb_bool src_sb_flag = TRUE;  /* whether the walk found `str` single byte */
#endif

  p = RSTRING_PTR(str); pend = RSTRING_END(str);
#ifdef MRB_UTF8_STRING
  /* `inspect` passes a whole character through unescaped so it stays readable,
     which is why it reads the character at every byte. A single-byte string
     has none spelled in more than one byte: a byte-read one holds no
     characters at all, and one of nothing but ASCII holds only characters the
     escaping below writes out the same way. Both escape byte by byte, which is
     what `dump` on the same string already did, and neither reads a character
     to do it. */
  if (RSTR_SINGLE_BYTE_P(mrb_str_ptr(str))) inspect = FALSE;
#endif
  for (;p < pend; p++) {
    unsigned char c, cc;
#ifdef MRB_UTF8_STRING
    if (inspect) {
      mrb_int clen = mrb_utf8len(p, pend);
      /* A non-ASCII byte either begins a character of several bytes or begins
         no character at all, and either way `str` is not one byte per
         character. The escape below turns the second into `\xNN`, so `result`
         still is, and only a whole character copied across takes that from
         it. */
      if (NOASCII(*p)) src_sb_flag = FALSE;
      if (clen > 1) {
        mrb_str_cat(mrb, result, p, clen);
        p += clen-1;
        sb_flag = FALSE;
        continue;
      }
    }
#endif
    c = *p;
    if (c == '"'|| c == '\\' || (c == '#' && IS_EVSTR(p+1, pend))) {
      buf[0] = '\\'; buf[1] = c;
      mrb_str_cat(mrb, result, buf, 2);
      continue;
    }
    if (ISPRINT(c)) {
      buf[0] = c;
      mrb_str_cat(mrb, result, buf, 1);
      continue;
    }
    switch (c) {
      case '\n': cc = 'n'; break;
      case '\r': cc = 'r'; break;
      case '\t': cc = 't'; break;
      case '\f': cc = 'f'; break;
      case '\013': cc = 'v'; break;
      case '\010': cc = 'b'; break;
      case '\007': cc = 'a'; break;
      case 033: cc = 'e'; break;
      default: cc = 0; break;
    }
    buf[0] = '\\';
    if (cc) {
      buf[1] = (char)cc;
      mrb_str_cat(mrb, result, buf, 2);
    }
    else {
      buf[1] = 'x';
      buf[3] = escape_hexmap[c % 16]; c /= 16;
      buf[2] = escape_hexmap[c % 16];
      mrb_str_cat(mrb, result, buf, 4);
    }
  }
  mrb_str_cat_lit(mrb, result, "\"");
#ifdef MRB_UTF8_STRING
  if (inspect) {
    if (src_sb_flag) RSTR_CODERANGE_SET(mrb_str_ptr(str), MRB_STR_CODERANGE_7BIT);
    if (sb_flag) RSTR_CODERANGE_SET(mrb_str_ptr(result), MRB_STR_CODERANGE_7BIT);
  }
  else {
    RSTR_CODERANGE_SET(mrb_str_ptr(result), MRB_STR_CODERANGE_7BIT);
  }
#endif

  return result;
}

/*
 * @param mrb The mruby state.
 * @param str The receiver, modified in place.
 * @param idx The index or range, read as `mrb_str_aref()` reads it.
 * @param alen An optional length (if `idx` is an integer), or undef.
 * @param replace The replacement, which has to be a String already: anything
 *                else raises TypeError, before the range is looked at.
 *
 * Implements string element assignment (e.g. `str[idx] = replace`).
 */
void
mrb_str_aset(mrb_state *mrb, mrb_value str, mrb_value idx, mrb_value alen, mrb_value replace)
{
  mrb_int beg, len, charlen;

  mrb_ensure_string_type(mrb, replace);
  switch (str_convert_range(mrb, str, idx, alen, &beg, &len)) {
    case STR_OUT_OF_RANGE:
    default:
      mrb_raise(mrb, E_INDEX_ERROR, "string not matched");
    case STR_CHAR_RANGE:
      if (len < 0) {
        mrb_raisef(mrb, E_INDEX_ERROR, "negative length %v", alen);
      }
      charlen = mrb_str_char_len(mrb, str);
      if (beg < 0) { beg += charlen; }
      if (beg < 0 || beg > charlen) { str_out_of_index(mrb, idx); }
      /* fall through */
    case STR_CHAR_RANGE_CORRECTED:
      beg = mrb_str_char_to_byte(mrb, str, 0, beg);
      len = mrb_str_char_to_byte(mrb, str, beg, len);
      /* fall through */
    case STR_BYTE_RANGE_CORRECTED:
      if (mrb_int_add_overflow(beg, len, &len)) {
        mrb_raise(mrb, E_RUNTIME_ERROR, "string index too big");
      }
      str_replace_partial(mrb, str, beg, len, replace);
  }
}

/*
 * call-seq:
 *    str[int] = replace
 *    str[int, int] = replace
 *    str[range] = replace
 *    str[other_str] = replace
 *
 * Modify `self` by replacing the content of `self`.
 * The portion of the string affected is determined using the same criteria as +String#[]+.
 * The return value of this expression is `replace`.
 */
static mrb_value
mrb_str_aset_m(mrb_state *mrb, mrb_value str)
{
  mrb_value idx, alen, replace;

  switch (mrb_get_args(mrb, "oo|S!", &idx, &alen, &replace)) {
    case 2:
      replace = alen;
      alen = mrb_undef_value();
      break;
    case 3:
      break;
  }
  mrb_str_aset(mrb, str, idx, alen, replace);
  return replace;
}

#if defined(MRB_UTF8_STRING) && !defined(MRB_USE_ASCII_CTYPE)

/* What the walk below makes of an ASCII character. Each method keeps its own
   loop over a string that holds nothing but ASCII, so this is reached only for
   the ASCII characters of a string that holds others beside them. */
static int
ascii_case_conv(int c, enum mrb_case_mode mode, mrb_bool first)
{
  switch (mode) {
  case MRB_CASE_UP:
    return TOUPPER(c);
  case MRB_CASE_CAPITALIZE:
    return first ? TOUPPER(c) : TOLOWER(c);
  case MRB_CASE_SWAP:
    return ISUPPER(c) ? TOLOWER(c) : TOUPPER(c);
  default:
    return TOLOWER(c);
  }
}

static enum mrb_case_kind
case_kind_of(enum mrb_case_mode mode, mrb_bool first)
{
  switch (mode) {
  case MRB_CASE_UP:
    return MRB_CASE_KIND_UPPER;
  case MRB_CASE_CAPITALIZE:
    return first ? MRB_CASE_KIND_TITLE : MRB_CASE_KIND_LOWER;
  case MRB_CASE_SWAP:
    return MRB_CASE_KIND_SWAP;
  case MRB_CASE_FOLD:
    return MRB_CASE_KIND_FOLD;
  default:
    return MRB_CASE_KIND_LOWER;
  }
}

/* Room in `o` for `need` more bytes past the `len` already written. The answer
   is built with its length held apart from the string, so this grows the
   buffer the way an append does without the questions an append from anywhere
   has to ask: what is written here is this walk's own bytes, and where they
   go is not somewhere the string can already be. */
static char*
case_out_room(mrb_state *mrb, struct RString *o, mrb_int len, mrb_int need)
{
  mrb_int capa = RSTR_CAPA(o);

  if (capa - len < need) {
    mrb_int want;
    if (mrb_int_add_overflow(len, need, &want)) {
      mrb_raise(mrb, E_ARGUMENT_ERROR, "string size too big");
    }
    while (capa < want) {
      if (mrb_int_mul_overflow(capa, 2, &capa)) {
        capa = want;
        break;
      }
    }
    /* Leaving the buffer takes the string's length with it, and what an
       embedded string carries over is that many bytes: told nothing, it would
       carry over none of what has been written so far. */
    RSTR_SET_LEN(o, len);
    resize_capa(mrb, o, capa);
  }
  return RSTR_PTR(o) + len;
}

/* Convert a string that holds characters the tables can speak about. A mapping
   changes how many bytes a character takes ("K" U+212A lower cases to the one
   byte of "k"), so the answer is built beside the string rather than over it,
   and the string takes the buffer's bytes at the end. */
static mrb_bool
str_case_convert_utf8(mrb_state *mrb, mrb_value str, enum mrb_case_mode mode)
{
  struct RString *s = mrb_str_ptr(str);
  const char *p = RSTR_PTR(s);
  const char *pend = p + RSTR_LEN(s);
  mrb_value out = mrb_str_new_capa(mrb, RSTR_LEN(s));
  struct RString *o = mrb_str_ptr(out);
  mrb_int dlen = 0;
  mrb_bool modify = FALSE;
  mrb_bool ascii_only = TRUE;
  mrb_bool first = TRUE;

  while (p < pend) {
    /* Room for whatever one character can map to, so neither branch below has
       to ask again for the character it is about to write. */
    char *d = case_out_room(mrb, o, dlen, MRB_UNI_CASE_MAX_BYTES);

    if ((unsigned char)*p < 0x80) {
      /* ASCII has no mapping to look up and takes one byte of the answer per
         byte of the source, so a run of it is converted where it stands.
         Reaching the tables for it, or the buffer through an append, is what
         made a string of ASCII with one character among it cost as much per
         byte as one made of characters. The run stops where the buffer does,
         and the turn of the loop after it is what grows the buffer. */
      const char *dend = RSTR_PTR(o) + RSTR_CAPA(o);
      do {
        int c = (unsigned char)*p++;
        int r = ascii_case_conv(c, mode, first);
        first = FALSE;
        if (r != c) modify = TRUE;
        *d++ = (char)r;
      } while (p < pend && (unsigned char)*p < 0x80 && d < dend);
      dlen = (mrb_int)(d - RSTR_PTR(o));
      continue;
    }

    const char *src = p;
    mrb_int clen;
    uint32_t cp = mrb_utf8_decode(p, pend, &clen);
    mrb_int n;

    /* A run of bytes that spells no character has no case to convert, and
       answering as though it were the byte it starts with would hand back a
       string neither its own reading nor the caller asked for. */
    if (clen == 1) {
      mrb_raise(mrb, E_ARGUMENT_ERROR, "input string invalid");
    }
    n = mrb_uni_case_map(case_kind_of(mode, first), cp, d);
    /* A character with no mapping stands as it is. */
    if (n == 0) {
      memcpy(d, src, (size_t)clen);
      n = clen;
    }
    p += clen;
    first = FALSE;

    if (n != clen || memcmp(d, src, (size_t)n) != 0) modify = TRUE;
    /* Only what a mapping wrote can be asked about here: a character maps to
       characters, and ASCII maps to ASCII, so the run above answers itself. */
    for (mrb_int i = 0; i < n; i++) {
      if ((unsigned char)d[i] & 0x80) ascii_only = FALSE;
    }
    dlen += n;
  }

  if (!modify) return FALSE;

  RSTR_SET_LEN(o, dlen);
  RSTR_PTR(o)[dlen] = '\0';

  /* Every byte of the source spelled a character, since the walk refuses one
     that does not, and every mapping spells characters, so what was written
     is sound. Nothing but ASCII is the stronger answer where it holds. */
  RSTR_CODERANGE_SET(o, ascii_only ? MRB_STR_CODERANGE_7BIT
                                   : MRB_STR_CODERANGE_VALID);
  str_replace(mrb, s, o);
  return TRUE;
}

int
mrb_str_case_convert_unicode(mrb_state *mrb, mrb_value str, enum mrb_case_mode mode)
{
  struct RString *s = mrb_str_ptr(str);

  /* A string of nothing but ASCII holds no character the tables speak about,
     and one read as bytes holds no characters at all. Neither is this walk's
     to make, so both go back to the caller's own loop, which converts the
     bytes where they stand. A string that has not been walked yet is walked
     for it: reading it through is what the loop below does anyway, and this
     way an ASCII one is spared the second string the walk builds beside it.
     The byte reading is asked about first, since a string read as bytes must
     not be recorded as holding one character per byte. */
  if (RSTR_BINARY_P(s) || str_ascii_p(s)) return -1;

  mrb_str_modify_keep_cr(mrb, s);

  return str_case_convert_utf8(mrb, str, mode) ? 1 : 0;
}

#endif  /* MRB_UTF8_STRING && !MRB_USE_ASCII_CTYPE */

/* 15.2.10.5.8  */
/*
 *  call-seq:
 *     str.capitalize!   => str or nil
 *
 *  Modifies *str* by converting the first character to uppercase and the
 *  remainder to lowercase. Returns `nil` if no changes are made.
 *
 *     a = "hello"
 *     a.capitalize!   #=> "Hello"
 *     a               #=> "Hello"
 *     a.capitalize!   #=> nil
 */
static mrb_value
mrb_str_capitalize_bang(mrb_state *mrb, mrb_value str)
{
  int uc = mrb_str_case_convert_unicode(mrb, str, MRB_CASE_CAPITALIZE);
  if (uc >= 0) return uc ? str : mrb_nil_value();

  mrb_bool modify = FALSE;
  struct RString *s = mrb_str_ptr(str);
  mrb_int len = RSTR_LEN(s);

  mrb_str_modify_keep_cr(mrb, s);
  char *p = RSTR_PTR(s);
  char *pend = RSTR_PTR(s) + len;
  if (len == 0 || p == NULL) return mrb_nil_value();
  if (ISLOWER(*p)) {
    *p = TOUPPER(*p);
    modify = TRUE;
  }
  while (++p < pend) {
    if (ISUPPER(*p)) {
      *p = TOLOWER(*p);
      modify = TRUE;
    }
  }
  if (modify) return str;
  return mrb_nil_value();
}

/* 15.2.10.5.7  */
/*
 *  call-seq:
 *     str.capitalize   => new_str
 *
 *  Returns a copy of *str* with the first character converted to uppercase
 *  and the remainder to lowercase. Where a character has a title case apart
 *  from its upper case, the first one takes that ("" to "").
 *
 *     "hello".capitalize    #=> "Hello"
 *     "HELLO".capitalize    #=> "Hello"
 *     "123ABC".capitalize   #=> "123abc"
 */
static mrb_value
mrb_str_capitalize(mrb_state *mrb, mrb_value self)
{
  mrb_value str = mrb_str_dup(mrb, self);
  mrb_str_capitalize_bang(mrb, str);
  return str;
}

/* 15.2.10.5.10  */
/*
 *  call-seq:
 *     str.chomp!(separator="\n")   => str or nil
 *
 *  Modifies *str* in place as described for `String#chomp`,
 *  returning *str*, or `nil` if no modifications were made.
 */
static mrb_value
mrb_str_chomp_bang(mrb_state *mrb, mrb_value str)
{
  mrb_value rs;
  mrb_int argc = mrb_get_args(mrb, "|S", &rs);
  struct RString *s = mrb_str_ptr(str);

  mrb_str_modify_keep_cr(mrb, s);
  mrb_int len = RSTR_LEN(s);
  if (argc == 0) {
    if (len == 0) return mrb_nil_value();
  smart_chomp:
    if (RSTR_PTR(s)[len-1] == '\n') {
      RSTR_SET_LEN(s, RSTR_LEN(s) - 1);
      if (RSTR_LEN(s) > 0 &&
          RSTR_PTR(s)[RSTR_LEN(s)-1] == '\r') {
        RSTR_SET_LEN(s, RSTR_LEN(s) - 1);
      }
    }
    else if (RSTR_PTR(s)[len-1] == '\r') {
      RSTR_SET_LEN(s, RSTR_LEN(s) - 1);
    }
    else {
      return mrb_nil_value();
    }
    RSTR_PTR(s)[RSTR_LEN(s)] = '\0';
    return str;
  }

  if (len == 0 || mrb_nil_p(rs)) return mrb_nil_value();
  /* see str_index_str(): a separator that spells no character ends nothing */
  if (!mrb_str_valid_encoding_p(mrb, rs)) return mrb_nil_value();
  char *p = RSTR_PTR(s);
  mrb_int rslen = RSTRING_LEN(rs);
  if (rslen == 0) {
    while (len>0 && p[len-1] == '\n') {
      len--;
      if (len>0 && p[len-1] == '\r')
        len--;
    }
    if (len < RSTR_LEN(s)) {
      RSTR_SET_LEN(s, len);
      p[len] = '\0';
      return str;
    }
    return mrb_nil_value();
  }
  if (rslen > len) return mrb_nil_value();
  mrb_int newline = RSTRING_PTR(rs)[rslen-1];
  if (rslen == 1 && newline == '\n')
    newline = RSTRING_PTR(rs)[rslen-1];
  if (rslen == 1 && newline == '\n')
    goto smart_chomp;

  char *pp = p + len - rslen;
  if (p[len-1] == newline &&
     (rslen  new_str
 *
 *  Returns a new `String` with the given record separator removed
 *  from the end of *str* (if present). `chomp` also removes
 *  carriage return characters (that is it will remove `\n`,
 *  `\r`, and `\r\n`).
 *
 *     "hello".chomp            #=> "hello"
 *     "hello\n".chomp          #=> "hello"
 *     "hello\r\n".chomp        #=> "hello"
 *     "hello\n\r".chomp        #=> "hello\n"
 *     "hello\r".chomp          #=> "hello"
 *     "hello \n there".chomp   #=> "hello \n there"
 *     "hello".chomp("llo")     #=> "he"
 */
static mrb_value
mrb_str_chomp(mrb_state *mrb, mrb_value self)
{
  mrb_value str = mrb_str_dup(mrb, self);
  mrb_str_chomp_bang(mrb, str);
  return str;
}

/* 15.2.10.5.12 */
/*
 *  call-seq:
 *     str.chop!   => str or nil
 *
 *  Processes *str* as for `String#chop`, returning *str*,
 *  or `nil` if *str* is the empty string.  See also
 *  `String#chomp!`.
 */
static mrb_value
mrb_str_chop_bang(mrb_state *mrb, mrb_value str)
{
  struct RString *s = mrb_str_ptr(str);

  mrb_str_modify_keep_cr(mrb, s);
  if (RSTR_LEN(s) > 0) {
    /* The last position of a single-byte string is its last byte. */
    mrb_int len = RSTR_LEN(s) - 1;
#ifdef MRB_UTF8_STRING
    if (!RSTR_SINGLE_BYTE_P(s)) {
      /* The last character starts at the head of the one covering the last
         byte, which is read backwards from there rather than by walking the
         whole string. */
      const char* t = RSTR_PTR(s);
      const char* e = t + RSTR_LEN(s);
      len = mrb_utf8_char_head(t, e-1, e) - t;
    }
#endif
    if (RSTR_PTR(s)[len] == '\n') {
      if (len > 0 &&
          RSTR_PTR(s)[len-1] == '\r') {
        len--;
      }
    }
#ifdef MRB_UTF8_STRING
    /* see mrb_str_chomp_bang(): the character cut here is the last one, so a
       non-ASCII lead byte at `len` is the whole of what leaves the string, and
       it can have been the last non-ASCII there was. */
    if ((signed char)RSTR_PTR(s)[len] < 0) {
      RSTR_CODERANGE_SET(s, MRB_STR_CODERANGE_UNKNOWN);
    }
#endif
    RSTR_SET_LEN(s, len);
    RSTR_PTR(s)[len] = '\0';
    return str;
  }
  return mrb_nil_value();
}

/* 15.2.10.5.11 */
/*
 *  call-seq:
 *     str.chop   => new_str
 *
 *  Returns a new `String` with the last character removed.  If the
 *  string ends with `\r\n`, both characters are removed. Applying
 *  `chop` to an empty string returns an empty
 *  string. `String#chomp` is often a safer alternative, as it leaves
 *  the string unchanged if it doesn't end in a record separator.
 *
 *     "string\r\n".chop   #=> "string"
 *     "string\n\r".chop   #=> "string\n"
 *     "string\n".chop     #=> "string"
 *     "string".chop       #=> "strin"
 *     "x".chop            #=> ""
 */
static mrb_value
mrb_str_chop(mrb_state *mrb, mrb_value self)
{
  mrb_value str = mrb_str_dup(mrb, self);
  mrb_str_chop_bang(mrb, str);
  return str;
}

/* 15.2.10.5.14 */
/*
 *  call-seq:
 *     str.downcase!   => str or nil
 *
 *  Downcases the contents of *str*, returning `nil` if no
 *  changes were made.
 */
static mrb_value
mrb_str_downcase_bang(mrb_state *mrb, mrb_value str)
{
  int uc = mrb_str_case_convert_unicode(mrb, str, MRB_CASE_DOWN);
  if (uc >= 0) return uc ? str : mrb_nil_value();

  char *p, *pend;
  mrb_bool modify = FALSE;
  struct RString *s = mrb_str_ptr(str);

  mrb_str_modify_keep_cr(mrb, s);
  p = RSTR_PTR(s);
  pend = RSTR_PTR(s) + RSTR_LEN(s);
  while (p < pend) {
    if (ISUPPER(*p)) {
      *p = TOLOWER(*p);
      modify = TRUE;
    }
    p++;
  }

  if (modify) return str;
  return mrb_nil_value();
}

/* 15.2.10.5.13 */
/*
 *  call-seq:
 *     str.downcase   => new_str
 *
 *  Returns a copy of *str* with all uppercase letters replaced with their
 *  lowercase counterparts. The operation is locale insensitive. A build that
 *  reads a string as characters maps every character Unicode gives a lower
 *  case; one that reads it as bytes maps 'A' to 'Z' alone.
 *
 *     "hEllO".downcase   #=> "hello"
 */
static mrb_value
mrb_str_downcase(mrb_state *mrb, mrb_value self)
{
  mrb_value str = mrb_str_dup(mrb, self);
  mrb_str_downcase_bang(mrb, str);
  return str;
}

/* 15.2.10.5.16 */
/*
 *  call-seq:
 *     str.empty?   => true or false
 *
 *  Returns `true` if *str* has a length of zero.
 *
 *     "hello".empty?   #=> false
 *     "".empty?        #=> true
 */
static mrb_value
mrb_str_empty_p(mrb_state *mrb, mrb_value self)
{
  struct RString *s = mrb_str_ptr(self);

  return mrb_bool_value(RSTR_LEN(s) == 0);
}

/* 15.2.10.5.17 */
/*
 * call-seq:
 *   str.eql?(other)   => true or false
 *
 * Two strings are equal if the have the same length and content.
 */
static mrb_value
mrb_str_eql(mrb_state *mrb, mrb_value self)
{
  mrb_value str2 = mrb_get_arg1(mrb);
  mrb_bool eql_p = (mrb_string_p(str2)) && str_eql(mrb, self, str2);

  return mrb_bool_value(eql_p);
}

/*
 * @param mrb The mruby state.
 * @param str The mruby string from which to take a substring.
 * @param beg The starting character index of the substring.
 * @param len The length in characters of the substring.
 * @return A new mruby string representing the substring, or nil if out of bounds.
 *
 * Creates a new mruby string that is a substring of an existing string.
 * This function considers character indices (which might differ from byte indices
 * if UTF-8 is enabled) and length.
 * Handles negative indices and adjusts length to fit within string boundaries.
 */
MRB_API mrb_value
mrb_str_substr(mrb_state *mrb, mrb_value str, mrb_int beg, mrb_int len)
{
  return str_substr(mrb, str, beg, len);
}

/*
 * 32-bit magic FNV-0 and FNV-1 prime
b */
#define FNV_32_PRIME ((uint32_t)0x01000193)
#define FNV1_32_INIT ((uint32_t)0x811c9dc5)

uint32_t
mrb_byte_hash_step(const uint8_t *s, mrb_int len, uint32_t hval)
{
  const uint8_t *send = s + len;

  /*
   * FNV-1a hash each octet in the buffer
   */
  while (s < send) {
    /* xor the bottom with the current octet */
    hval ^= (uint32_t)*s++;

    /* multiply by the 32-bit FNV magic prime mod 2^32 */
#if defined(NO_FNV_GCC_OPTIMIZATION)
    hval *= FNV_32_PRIME;
#else
    hval += (hval 0);
  *p2 = '\0';
  RSTR_SET_LEN(p_str, (mrb_int)(p2 - RSTR_PTR(p_str)));

  while (p1 < p2) {
    const char  c = *p1;
    *p1++ = *--p2;
    *p2 = c;
  }

  return mrb_obj_value(p_str);
}

static inline void
str_reverse(char *p, char *e)
{
  char c;

  while (p < e) {
    c = *p;
    *p++ = *e;
    *e-- = c;
  }
}

/* 15.2.10.5.30 */
/*
 *  call-seq:
 *     str.reverse!   => str
 *
 *  Reverses *str* in place.
 */
static mrb_value
mrb_str_reverse_bang(mrb_state *mrb, mrb_value str)
{
  struct RString *s = mrb_str_ptr(str);
  char *p, *e;

  /* Reversing writes the string's own bytes back in another order, and both
     paths below leave every character whole, so a string that read as UTF-8
     still does: both write through mrb_str_modify_keep_cr(). A string already
     read as broken is the one this cannot answer for, since bytes that spell
     nothing where they stand can spell a character turned around, and that is
     the string the helper asks again on its own. */

#ifdef MRB_UTF8_STRING
  /* mrb_str_char_len() walks the string and records what it finds. The
     multi-byte path turns each character's bytes around where they stand and
     then turns the whole buffer around, which puts the characters back in the
     reverse order with each one whole, so that record still holds and the
     next asker is spared the same walk. */
  mrb_int utf8_len = mrb_str_char_len(mrb, str);
  mrb_int len = RSTR_LEN(s);

  if (utf8_len < 2) {
    /* One character or none reverses into itself and returns here, ahead of
       the str_modify_keep_cr() below that turns a frozen receiver away. The
       call is destructive at any length, so it is asked here. */
    mrb_check_frozen(mrb, s);
    return str;
  }
  if (utf8_len < len) {
    mrb_str_modify_keep_cr(mrb, s);
    p = RSTR_PTR(s);
    e = p + RSTR_LEN(s);
    while (p 1) {
    mrb_str_modify_keep_cr(mrb, s);
    goto bytes;
  }
  /* As above, for a build that reads one character per byte. */
  mrb_check_frozen(mrb, s);
  return str;

 bytes:
  p = RSTR_PTR(s);
  e = p + RSTR_LEN(s) - 1;
  str_reverse(p, e);
  return str;
}

/* ---------------------------------- */
/* 15.2.10.5.29 */
/*
 *  call-seq:
 *     str.reverse   => new_str
 *
 *  Returns a new string with the characters from *str* in reverse order.
 *
 *     "stressed".reverse   #=> "desserts"
 */
static mrb_value
mrb_str_reverse(mrb_state *mrb, mrb_value str)
{
  mrb_value str2 = mrb_str_dup(mrb, str);
  mrb_str_reverse_bang(mrb, str2);
  return str2;
}

/*
 *  call-seq:
 *    byterindex(substring, offset = self.bytesize) -> integer or nil
 *
 *  Returns the \Integer byte-based index of the _last_ occurrence of the given `substring`,
 *  or `nil` if none found:
 *
 *    'foo'.byterindex('f') # => 0
 *    'foo'.byterindex('o') # => 2
 *    'foo'.byterindex('oo') # => 1
 *    'foo'.byterindex('ooo') # => nil
 */
static mrb_value
mrb_str_byterindex_m(mrb_state *mrb, mrb_value str)
{
  mrb_int len = RSTRING_LEN(str);
  mrb_value sub;
  mrb_int pos;

  if (mrb_get_args(mrb, "S|i", &sub, &pos) == 1) {
    pos = len;
  }
  else {
    if (pos < 0) {
      pos += len;
      if (pos < 0) {
        return mrb_nil_value();
      }
    }
    if (pos > len) pos = len;
  }
  mrb_str_check_byte_pos(mrb, str, pos);
  /* see str_index_str() */
  if (!mrb_str_valid_encoding_p(mrb, sub)) return mrb_nil_value();
  pos = str_byterindex(str, sub, pos);
  if (pos < 0) {
    return mrb_nil_value();
  }
  return mrb_int_value(mrb, pos);
}

/* 15.2.10.5.31 */
/*
 *  call-seq:
 *     str.rindex(substring [, offset])   => int or nil
 *
 *  Returns the index of the last occurrence of the given *substring*.
 *  Returns `nil` if not found. If the second parameter is
 *  present, it specifies the position in the string to end the
 *  search---characters beyond this point will not be considered.
 *
 *     "hello".rindex('e')             #=> 1
 *     "hello".rindex('l')             #=> 3
 *     "hello".rindex('a')             #=> nil
 *     "hello".rindex('l', 2)          #=> 2
 */
#ifdef MRB_UTF8_STRING
static mrb_value
mrb_str_rindex_m(mrb_state *mrb, mrb_value str)
{
  if (mrb_str_single_byte_p(mrb, str)) {
    return mrb_str_byterindex_m(mrb, str);
  }

  mrb_value sub;
  mrb_int pos;

  if (mrb_get_args(mrb, "S|i", &sub, &pos) == 1) {
    pos = RSTRING_LEN(str);
  }
  else if (pos >= 0) {
    pos = mrb_str_char_to_byte(mrb, str, 0, pos);
  }
  else {
    const char *p = RSTRING_PTR(str);
    const char *send = RSTRING_END(str);
    const char *e = send;
    /* a negative `pos` counts characters back from the end, and landing on the
       first character is the last step that stays in the string */
    while (pos < 0) {
      if (e == p) return mrb_nil_value();
      e = mrb_utf8_char_head(p, e-1, send);
      pos++;
    }
    pos = (mrb_int)(e - p);
  }
  /* see str_index_str() */
  if (!mrb_str_valid_encoding_p(mrb, sub)) return mrb_nil_value();
  pos = str_char_rindex(str, sub, pos);
  if (pos >= 0) {
    pos = mrb_str_byte_to_char(mrb, str, pos);
    if (pos < 0) return mrb_nil_value();
    return mrb_int_value(mrb, pos);
  }
  return mrb_nil_value();
}
#else
#define mrb_str_rindex_m mrb_str_byterindex_m
#endif

/* 15.2.10.5.35 */

/*
 *  call-seq:
 *     str.split(separator=nil, [limit])   => anArray
 *
 *  Divides *str* into substrings based on a delimiter, returning an array
 *  of these substrings.
 *
 *  If *separator* is a `String`, then its contents are used as
 *  the delimiter when splitting *str*. If *separator* is a single
 *  space, *str* is split on whitespace, with leading whitespace and runs
 *  of contiguous whitespace characters ignored.
 *
 *  If *separator* is omitted or `nil` (which is the default),
 *  *str* is split on whitespace as if ' ' were specified.
 *
 *  If the *limit* parameter is omitted, trailing null fields are
 *  suppressed. If *limit* is a positive number, at most that number of
 *  fields will be returned (if *limit* is `1`, the entire
 *  string is returned as the only entry in an array). If negative, there is no
 *  limit to the number of fields returned, and trailing null fields are not
 *  suppressed.
 *
 *     " now's  the time".split        #=> ["now's", "the", "time"]
 *     " now's  the time".split(' ')   #=> ["now's", "the", "time"]
 *
 *     "mellow yellow".split("ello")   #=> ["m", "w y", "w"]
 *     "1,2,,3,4,,".split(',')         #=> ["1", "2", "", "3", "4"]
 *     "1,2,,3,4,,".split(',', 4)      #=> ["1", "2", "", "3,4,,"]
 *     "1,2,,3,4,,".split(',', -4)     #=> ["1", "2", "", "3", "4", "", ""]
 */

static mrb_value
mrb_str_split_m(mrb_state *mrb, mrb_value str)
{
  mrb_value spat = mrb_nil_value();
  enum {awk, string} split_type = string;
  mrb_int i = 0;
  mrb_int lim = 0;
  mrb_value tmp;

  mrb_int argc = mrb_get_args(mrb, "|oi", &spat, &lim);
  mrb_bool lim_p = (lim > 0 && argc == 2);
  if (argc == 2) {
    if (lim == 1) {
      if (RSTRING_LEN(str) == 0)
        return mrb_ary_new_capa(mrb, 0);
      return mrb_ary_new_from_values(mrb, 1, &str);
    }
    i = 1;
  }

  if (argc == 0 || mrb_nil_p(spat)) {
    split_type = awk;
  }
  else if (!mrb_string_p(spat)) {
    mrb_raise(mrb, E_TYPE_ERROR, "expected String");
  }
  else if (RSTRING_LEN(spat) == 1 && RSTRING_PTR(spat)[0] == ' ') {
    split_type = awk;
  }

  mrb_value result = mrb_ary_new(mrb);
  mrb_int beg = 0;
  if (split_type == awk) {
    mrb_bool skip = TRUE;
    mrb_int str_len = RSTRING_LEN(str);
    mrb_int idx = beg;
    mrb_int end = beg;
    int ai = mrb_gc_arena_save(mrb);
    unsigned int c;

    while (idx < str_len) {
      c = (unsigned char)RSTRING_PTR(str)[idx++];
      if (skip) {
        if (ISSPACE(c)) {
          beg = idx;
        }
        else {
          end = idx;
          skip = FALSE;
          if (lim_p && lim  0) {
        end = mrb_memsearch(RSTRING_PTR(spat), pat_len, RSTRING_PTR(str)+idx, str_len - idx);
        if (end < 0) break;
      }
      else {
        end = mrb_str_char_to_byte(mrb, str, idx, 1);
      }
      mrb_ary_push(mrb, result, mrb_str_byte_subseq(mrb, str, idx, end));
      mrb_gc_arena_restore(mrb, ai);
      idx += end + pat_len;
      if (lim_p && lim  0 && (lim_p || RSTRING_LEN(str) > beg || lim < 0)) {
    if (RSTRING_LEN(str) == beg) {
      tmp = mrb_str_new(mrb, 0, 0);
    }
    else {
      tmp = mrb_str_byte_subseq(mrb, str, beg, RSTRING_LEN(str)-beg);
    }
    mrb_ary_push(mrb, result, tmp);
  }
  if (!lim_p && lim == 0) {
    mrb_int len;
    while ((len = RARRAY_LEN(result)) > 0 &&
           (tmp = RARRAY_PTR(result)[len-1], RSTRING_LEN(tmp) == 0))
      mrb_ary_pop(mrb, result);
  }

  return result;
}

static mrb_bool
trailingbad(const char *str, const char *p, const char *pend)
{
  if (p == str) return TRUE;             /* no number */
  if (*(p - 1) == '_') return TRUE;      /* trailing '_' */
  while (p 0
 *     "0a".to_i(16)            #=> 10
 *     "hello".to_i             #=> 0
 *     "1100101".to_i(2)        #=> 101
 *     "1100101".to_i(8)        #=> 294977
 *     "1100101".to_i(10)       #=> 1100101
 *     "1100101".to_i(16)       #=> 17826049
 */
static mrb_value
mrb_str_to_i(mrb_state *mrb, mrb_value self)
{
  mrb_int base = 10;

  mrb_get_args(mrb, "|i", &base);
  if (base < 0 || 36 < base) {
    mrb_raisef(mrb, E_ARGUMENT_ERROR, "illegal radix %i", base);
  }
  return mrb_str_to_integer(mrb, self, base, FALSE);
}

#ifndef MRB_NO_FLOAT
/* Internal helper for mrb_str_to_dbl */
static double
mrb_str_len_to_dbl(mrb_state *mrb, const char *s, size_t len, mrb_bool badcheck)
{
  char buf[DBL_DIG * 4 + 20];
  const char *p = s, *p2;
  const char *pend = p + len;
  char *end;
  char *n;
  char prev = 0;
  double d;
  mrb_bool dot = FALSE;

  if (!p) return 0.0;
  while (p 2 && p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) {
    mrb_value x;

    if (!badcheck) return 0.0;
    x = mrb_str_len_to_integer(mrb, p, pend-p, 0, badcheck);
    if (mrb_integer_p(x))
      d = (double)mrb_integer(x);
    else /* if (mrb_float_p(x)) */
      d = mrb_float(x);
    return d;
  }
  while (p < pend) {
    if (!*p) {
      if (badcheck) {
        mrb_raise(mrb, E_ARGUMENT_ERROR, "string for Float contains null byte");
        /* not reached */
      }
      pend = p;
      p = p2;
      goto nocopy;
    }
    if (!badcheck && *p == ' ') {
      pend = p;
      p = p2;
      goto nocopy;
    }
    if (*p == '_') break;
    p++;
  }
  p = p2;
  n = buf;
  while (p < pend) {
    char c = *p++;
    if (c == '.') dot = TRUE;
    if (c == '_') {
      /* remove an underscore between digits */
      if (n == buf || !ISDIGIT(prev) || p == pend) {
        if (badcheck) goto bad;
        break;
      }
    }
    else if (badcheck && prev == '_' && !ISDIGIT(c)) goto bad;
    else {
      const char *bend = buf+sizeof(buf)-1;
      if (n==bend) {            /* buffer overflow */
        if (dot) break;         /* cut off remaining fractions */
        return INFINITY;
      }
      *n++ = c;
    }
    prev = c;
  }
  *n = '\0';
  p = buf;
  pend = n;
nocopy:
  if (mrb_read_float(p, &end, &d) == FALSE) {
    if (badcheck) {
bad:
      mrb_raisef(mrb, E_ARGUMENT_ERROR, "invalid string for float(%!s)", s);
      /* not reached */
    }
    return 0.0;
  }
  if (badcheck) {
    if (!end || p == end) goto bad;
    while (end 1234.5
 *     "45.67 degrees".to_f   #=> 45.67
 *     "thx1138".to_f         #=> 0.0
 */
static mrb_value
mrb_str_to_f(mrb_state *mrb, mrb_value self)
{
  return mrb_float_value(mrb, mrb_str_to_dbl(mrb, self, FALSE));
}
#endif

/* 15.2.10.5.40 */
/*
 *  call-seq:
 *     str.to_s     => str
 *
 *  Returns the receiver.
 */
static mrb_value
mrb_str_to_s(mrb_state *mrb, mrb_value self)
{
  if (mrb_obj_class(mrb, self) != mrb->string_class) {
    return mrb_str_dup(mrb, self);
  }
  return self;
}

/* 15.2.10.5.43 */
/*
 *  call-seq:
 *     str.upcase!   => str or nil
 *
 *  Upcases the contents of *str*, returning `nil` if no changes
 *  were made.
 */
static mrb_value
mrb_str_upcase_bang(mrb_state *mrb, mrb_value str)
{
  int uc = mrb_str_case_convert_unicode(mrb, str, MRB_CASE_UP);
  if (uc >= 0) return uc ? str : mrb_nil_value();

  struct RString *s = mrb_str_ptr(str);
  char *p, *pend;
  mrb_bool modify = FALSE;

  mrb_str_modify_keep_cr(mrb, s);
  p = RSTRING_PTR(str);
  pend = RSTRING_END(str);
  while (p < pend) {
    if (ISLOWER(*p)) {
      *p = TOUPPER(*p);
      modify = TRUE;
    }
    p++;
  }

  if (modify) return str;
  return mrb_nil_value();
}

/* 15.2.10.5.42 */
/*
 *  call-seq:
 *     str.upcase   => new_str
 *
 *  Returns a copy of *str* with all lowercase letters replaced with their
 *  uppercase counterparts. The operation is locale insensitive. A build that
 *  reads a string as characters maps every character Unicode gives an upper
 *  case, which can spell more characters than it was handed ("" to "SS"); one
 *  that reads it as bytes maps 'a' to 'z' alone.
 *
 *     "hEllO".upcase   #=> "HELLO"
 */
static mrb_value
mrb_str_upcase(mrb_state *mrb, mrb_value self)
{
  mrb_value str = mrb_str_dup(mrb, self);
  mrb_str_upcase_bang(mrb, str);
  return str;
}

/*
 *  call-seq:
 *     str.dump   -> new_str
 *
 *  Produces a version of *str* with all nonprinting characters replaced by
 *  `\nnn` notation and all special characters escaped.
 */
mrb_value
mrb_str_dump(mrb_state *mrb, mrb_value str)
{
  return str_escape(mrb, str, FALSE);
}

/* mrb_str_modify() for appending `addlen` bytes at the end of `s`.
   An append only touches [len, len+addlen), which no other sharer of the
   buffer can see, so the buffer copy that mrb_str_modify() would do can be
   skipped as long as the write stays inside the shared allocation. Growing
   past it still has to detach, but capacity grows geometrically, so the
   copies are amortized instead of one per append.
   `addlen` must not be negative: it would pass the capacity guard below and
   then lower `reserved`, handing bytes another sharer still reads to the
   appender. mrb_str_cat() rejects a length that does not fit beforehand.
   Returns the usable capacity of `s`. */
static mrb_int
str_modify_cat(mrb_state *mrb, struct RString *s, mrb_int addlen)
{
  mrb_assert(addlen >= 0);
  if (RSTR_SHARED_P(s)) {
    mrb_check_frozen(mrb, s);
    mrb_shared_string *shared = s->as.heap.aux.shared;
    mrb_int off = (mrb_int)(s->as.heap.ptr - shared->ptr);
    mrb_int capa = shared->capa - off;
    if (off + s->as.heap.len >= shared->reserved && addlen < capa - s->as.heap.len) {
      /* The appended bytes belong to `s` from now on, so no other sharer may
         write over them. */
      shared->reserved = off + s->as.heap.len + addlen;
      RSTR_CODERANGE_SET(s, MRB_STR_CODERANGE_UNKNOWN);
      return capa;
    }
  }
  mrb_str_modify(mrb, s);
  return RSTR_CAPA(s);
}

/*
 * @param mrb The mruby state.
 * @param str The mruby string to append to (modified in place).
 * @param ptr A pointer to the C string to append.
 * @param len The length of the C string to append.
 * @return The modified mruby string `str`.
 *
 * Appends a C string of a given length to an mruby string.
 * The mruby string `str` is modified in place. Handles resizing and
 * potential overlap if `ptr` is within `str`'s buffer.
 */
MRB_API mrb_value
mrb_str_cat(mrb_state *mrb, mrb_value str, const char *ptr, size_t len)
{
  struct RString *s = mrb_str_ptr(str);
  ptrdiff_t off = -1;

  /* An append of nothing writes nothing, but it is still an append, and the
     only frozen check on this path is the one the modify below runs. Asking
     here keeps `str  (size_t)MRB_INT_MAX ||
      mrb_int_add_overflow(RSTR_LEN(s), (mrb_int)len, &total)) {
  size_error:
    mrb_raise(mrb, E_ARGUMENT_ERROR, "string size too big");
  }
  /* The overlap has to be recognized against the buffer `ptr` was taken
     from, which is the one `s` holds now. `str_modify_cat()` either appends
     inside the shared allocation, where `ptr` stays valid and the offset is
     the same answer reached another way, or detaches `s` onto a fresh buffer
     and releases the old one, where `ptr` is neither inside the new buffer
     nor safe to read. Recording the offset ahead of the call covers both
     without having to know which path ran.

     `ptr` is allowed to come from anywhere, so it and `RSTR_PTR(s)` need not
     point into the same object, and relational comparison and subtraction
     between pointers that do not is undefined. Going through `uintptr_t`
     leaves both on integers, where the whole range is ordered. */
  uintptr_t ptr_addr = (uintptr_t)ptr;
  uintptr_t str_addr = (uintptr_t)RSTR_PTR(s);
  if (ptr_addr >= str_addr && ptr_addr  offset) {
        mrb_str_cat(mrb, result, RSTRING_PTR(self)+offset, RSTRING_LEN(self)-offset);
        self_taken = TRUE;
      }
      break;
    case '1': case '2': case '3':
    case '4': case '5': case '6':
    case '7': case '8': case '9':
      /* ignore sub-group match (no Regexp supported) */
      break;
    default:
      mrb_str_cat(mrb, result, &p[i-1], 2);
      break;
    }
  }
  /* The splice holds bytes of the replacement and of whatever the escapes
     copied in, so it is read as bytes exactly when one of those sources
     handed it byte-read bytes above ASCII, the same as any other append.

     A source is asked about as a whole, not about the part the escape
     actually copied: how a string is read is a property of the string, which
     is what CRuby asks too, so a `\`` that lands only on the ASCII head of a
     byte-read subject still reports it. Narrowing this to the copied bytes
     would answer differently from CRuby, not more precisely. */
  if ((RSTR_BINARY_P(mrb_str_ptr(replace)) && !str_ascii_p(mrb_str_ptr(replace))) ||
      (match_taken && RSTR_BINARY_P(mrb_str_ptr(pat)) && !str_ascii_p(mrb_str_ptr(pat))) ||
      (self_taken && RSTR_BINARY_P(mrb_str_ptr(self)) && !str_ascii_p(mrb_str_ptr(self)))) {
    RSTR_ENCODING_SET(mrb_str_ptr(result), MRB_STR_ENCODING_BINARY);
  }
  return result;
}


static mrb_value
str_bytesplice(mrb_state *mrb, mrb_value str, mrb_int idx1, mrb_int len1, mrb_value replace, mrb_int idx2, mrb_int len2)
{
  struct RString *s = RSTRING(str);
  if (idx1 < 0) {
    idx1 += RSTR_LEN(s);
  }
  if (idx2 < 0) {
    idx2 += RSTRING_LEN(replace);
  }
  if (RSTR_LEN(s) < idx1 || idx1 < 0 || RSTRING_LEN(replace) < idx2 || idx2 < 0) {
    mrb_raise(mrb, E_INDEX_ERROR, "index out of string");
  }
  if (len1 < 0 || len2 < 0) {
    mrb_raise(mrb, E_INDEX_ERROR, "negative length");
  }
  mrb_int n;
  if (mrb_int_add_overflow(idx1, len1, &n) || RSTR_LEN(s) < n) {
    len1 = RSTR_LEN(s) - idx1;
  }
  if (mrb_int_add_overflow(idx2, len2, &n) || RSTRING_LEN(replace) < n) {
    len2 = RSTRING_LEN(replace) - idx2;
  }
  /* Splicing the empty range at the end is an append: it writes nothing any
     sharer of the buffer can see, so mrb_str_cat() may grow the string inside
     that buffer where mrb_str_modify() below would copy the whole of it first.
     mrb_str_cat() checks the frozen receiver on every length, so the check
     mrb_str_modify() would have made is not lost. */
  if (idx1 == RSTR_LEN(s)) {
    return mrb_str_cat(mrb, str, RSTRING_PTR(replace) + idx2, (size_t)len2);
  }

  mrb_str_modify(mrb, s);
  if (len1 >= len2) {
    memmove(RSTR_PTR(s)+idx1, RSTRING_PTR(replace)+idx2, len2);
    if (len1 > len2) {
      memmove(RSTR_PTR(s)+idx1+len2, RSTR_PTR(s)+idx1+len1, RSTR_LEN(s)-(idx1+len1));
      RSTR_SET_LEN(s, RSTR_LEN(s)-(len1-len2));
    }
  }
  else { /* len1 < len2 */
    mrb_int slen = RSTR_LEN(s);
    mrb_str_resize(mrb, str, slen+len2-len1);
    memmove(RSTR_PTR(s)+idx1+len2, RSTR_PTR(s)+idx1+len1, slen-(idx1+len1));
    memmove(RSTR_PTR(s)+idx1, RSTRING_PTR(replace)+idx2, len2);
  }
  return str;
}

/*
 *  call-seq:
 *    bytesplice(index, length, str) -> string
 *    bytesplice(index, length, str, str_index, str_length) -> string
 *    bytesplice(range, str) -> string
 *    bytesplice(range, str, str_range) -> string
 *
 *  Replaces some or all of the content of `self` with `str`, and returns `self`.
 *  The portion of the string affected is determined using
 *  the same criteria as String#byteslice, except that `length` cannot be omitted.
 *  If the replacement string is not the same length as the text it is replacing,
 *  the string will be adjusted accordingly.
 *
 *  If `str_index` and `str_length`, or `str_range` are given, the content of `self`
 *  is replaced by str.byteslice(str_index, str_length) or str.byteslice(str_range);
 *  however the substring of `str` is not allocated as a new string.
 *
 *  The form that take an Integer will raise an IndexError if the value is out
 *  of range; the Range form will raise a RangeError.
 *  If the beginning or ending offset does not land on character (codepoint)
 *  boundary, an IndexError will be raised.
 */
static mrb_value
mrb_str_bytesplice(mrb_state *mrb, mrb_value str)
{
  mrb_int idx1, len1, idx2, len2;
  mrb_value range1, range2, replace;
  switch (mrb_get_argc(mrb)) {
  case 3:
    mrb_get_args(mrb, "ooo", &range1, &replace, &range2);
    if (mrb_integer_p(range1)) {
      mrb_get_args(mrb, "iiS", &idx1, &len1, &replace);
      return str_bytesplice(mrb, str, idx1, len1, replace, 0, RSTRING_LEN(replace));
    }
    mrb_ensure_string_type(mrb, replace);
    if (mrb_range_beg_len(mrb, range1, &idx1, &len1, RSTRING_LEN(str), FALSE) != MRB_RANGE_OK) break;
    if (mrb_range_beg_len(mrb, range2, &idx2, &len2, RSTRING_LEN(replace), FALSE) != MRB_RANGE_OK) break;
    return str_bytesplice(mrb, str, idx1, len1, replace, idx2, len2);
  case 5:
    mrb_get_args(mrb, "iiSii", &idx1, &len1, &replace, &idx2, &len2);
    return str_bytesplice(mrb, str, idx1, len1, replace, idx2, len2);
  case 2:
    mrb_get_args(mrb, "oS", &range1, &replace);
    if (mrb_range_beg_len(mrb, range1, &idx1, &len1, RSTRING_LEN(str), FALSE) == MRB_RANGE_OK) {
      return str_bytesplice(mrb, str, idx1, len1, replace, 0, RSTRING_LEN(replace));
    }
  default:
    break;
  }
  mrb_raise(mrb, E_ARGUMENT_ERROR, "wrong number of arumgnts");
}

static mrb_value
mrb_encoding(mrb_state *mrb, mrb_value self)
{
  mrb_get_args(mrb, "");
#ifdef MRB_UTF8_STRING
  return mrb_str_new_lit(mrb, "UTF-8");
#else
  return mrb_str_new_lit(mrb, "ASCII-8BIT");
#endif
}

/* ---------------------------*/
static const mrb_mt_entry string_rom_entries[] = {
  MRB_MT_ENTRY(mrb_str_bytesize,        MRB_SYM(bytesize),        MRB_ARGS_NONE()),
  MRB_MT_ENTRY(mrb_str_cmp_m,           MRB_OPSYM(cmp),           MRB_ARGS_REQ(1)),                   /* 15.2.10.5.1  */
  MRB_MT_ENTRY(mrb_str_equal_m,         MRB_OPSYM(eq),            MRB_ARGS_REQ(1)),                   /* 15.2.10.5.2  */
  MRB_MT_ENTRY(mrb_str_plus_m,          MRB_OPSYM(add),           MRB_ARGS_REQ(1)),                   /* 15.2.10.5.4  */
  MRB_MT_ENTRY(mrb_str_times,           MRB_OPSYM(mul),           MRB_ARGS_REQ(1)),                   /* 15.2.10.5.5  */
  MRB_MT_ENTRY(mrb_str_aref_m,          MRB_OPSYM(aref),          MRB_ARGS_ANY()),                    /* 15.2.10.5.6  */
  MRB_MT_ENTRY(mrb_str_aset_m,          MRB_OPSYM(aset),          MRB_ARGS_ANY()),
  MRB_MT_ENTRY(mrb_str_capitalize,      MRB_SYM(capitalize),      MRB_ARGS_NONE()),                   /* 15.2.10.5.7  */
  MRB_MT_ENTRY(mrb_str_capitalize_bang, MRB_SYM_B(capitalize),    MRB_ARGS_NONE()),                   /* 15.2.10.5.8  */
  MRB_MT_ENTRY(mrb_str_chomp,           MRB_SYM(chomp),           MRB_ARGS_ANY()),                    /* 15.2.10.5.9  */
  MRB_MT_ENTRY(mrb_str_chomp_bang,      MRB_SYM_B(chomp),         MRB_ARGS_ANY()),                    /* 15.2.10.5.10 */
  MRB_MT_ENTRY(mrb_str_chop,            MRB_SYM(chop),            MRB_ARGS_NONE()),                   /* 15.2.10.5.11 */
  MRB_MT_ENTRY(mrb_str_chop_bang,       MRB_SYM_B(chop),          MRB_ARGS_NONE()),                   /* 15.2.10.5.12 */
  MRB_MT_ENTRY(mrb_str_downcase,        MRB_SYM(downcase),        MRB_ARGS_NONE()),                   /* 15.2.10.5.13 */
  MRB_MT_ENTRY(mrb_str_downcase_bang,   MRB_SYM_B(downcase),      MRB_ARGS_NONE()),                   /* 15.2.10.5.14 */
  MRB_MT_ENTRY(mrb_str_empty_p,         MRB_SYM_Q(empty),         MRB_ARGS_NONE()),                   /* 15.2.10.5.16 */
  MRB_MT_ENTRY(mrb_str_eql,             MRB_SYM_Q(eql),           MRB_ARGS_REQ(1)),                   /* 15.2.10.5.17 */
  MRB_MT_ENTRY(mrb_str_hash_m,          MRB_SYM(hash),            MRB_ARGS_NONE()),                   /* 15.2.10.5.20 */
  MRB_MT_ENTRY(mrb_str_include,         MRB_SYM_Q(include),       MRB_ARGS_REQ(1)),                   /* 15.2.10.5.21 */
  MRB_MT_ENTRY(mrb_str_index_m,         MRB_SYM(index),           MRB_ARGS_ARG(1,1)),                 /* 15.2.10.5.22 */
  MRB_MT_ENTRY(mrb_str_init,            MRB_SYM(initialize),      MRB_ARGS_OPT(1) | MRB_MT_PRIVATE),  /* 15.2.10.5.23 */
  MRB_MT_ENTRY(mrb_str_replace,         MRB_SYM(initialize_copy), MRB_ARGS_REQ(1) | MRB_MT_PRIVATE),  /* 15.2.10.5.24 */
  MRB_MT_ENTRY(mrb_str_intern,          MRB_SYM(intern),          MRB_ARGS_NONE()),                   /* 15.2.10.5.25 */
  MRB_MT_ENTRY(mrb_str_size,            MRB_SYM(length),          MRB_ARGS_NONE()),                   /* 15.2.10.5.26 */
  MRB_MT_ENTRY(mrb_str_replace,         MRB_SYM(replace),         MRB_ARGS_REQ(1)),                   /* 15.2.10.5.28 */
  MRB_MT_ENTRY(mrb_str_reverse,         MRB_SYM(reverse),         MRB_ARGS_NONE()),                   /* 15.2.10.5.29 */
  MRB_MT_ENTRY(mrb_str_reverse_bang,    MRB_SYM_B(reverse),       MRB_ARGS_NONE()),                   /* 15.2.10.5.30 */
  MRB_MT_ENTRY(mrb_str_rindex_m,        MRB_SYM(rindex),          MRB_ARGS_ANY()),                    /* 15.2.10.5.31 */
  MRB_MT_ENTRY(mrb_str_size,            MRB_SYM(size),            MRB_ARGS_NONE()),                   /* 15.2.10.5.33 */
  MRB_MT_ENTRY(mrb_str_aref_m,          MRB_SYM(slice),           MRB_ARGS_ANY()),                    /* 15.2.10.5.34 */
  MRB_MT_ENTRY(mrb_str_split_m,         MRB_SYM(split),           MRB_ARGS_ANY()),                    /* 15.2.10.5.35 */
  MRB_MT_ENTRY(mrb_str_to_i,            MRB_SYM(to_i),            MRB_ARGS_ANY()),                    /* 15.2.10.5.39 */
  MRB_MT_ENTRY(mrb_str_to_s,            MRB_SYM(to_s),            MRB_ARGS_NONE()),                   /* 15.2.10.5.40 */
  MRB_MT_ENTRY(mrb_str_to_s,            MRB_SYM(to_str),          MRB_ARGS_NONE()),
  MRB_MT_ENTRY(mrb_str_intern,          MRB_SYM(to_sym),          MRB_ARGS_NONE()),                   /* 15.2.10.5.41 */
  MRB_MT_ENTRY(mrb_str_upcase,          MRB_SYM(upcase),          MRB_ARGS_NONE()),                   /* 15.2.10.5.42 */
  MRB_MT_ENTRY(mrb_str_upcase_bang,     MRB_SYM_B(upcase),        MRB_ARGS_NONE()),                   /* 15.2.10.5.43 */
  MRB_MT_ENTRY(mrb_str_inspect,         MRB_SYM(inspect),         MRB_ARGS_NONE()),                   /* 15.2.10.5.46(x) */
  MRB_MT_ENTRY(mrb_str_bytes,           MRB_SYM(bytes),           MRB_ARGS_NONE()),
  MRB_MT_ENTRY(mrb_str_getbyte,         MRB_SYM(getbyte),         MRB_ARGS_REQ(1)),
  MRB_MT_ENTRY(mrb_str_setbyte,         MRB_SYM(setbyte),         MRB_ARGS_REQ(2)),
  MRB_MT_ENTRY(mrb_str_byteindex_m,     MRB_SYM(byteindex),       MRB_ARGS_ARG(1,1)),
  MRB_MT_ENTRY(mrb_str_byterindex_m,    MRB_SYM(byterindex),      MRB_ARGS_ARG(1,1)),
  MRB_MT_ENTRY(mrb_str_byteslice,       MRB_SYM(byteslice),       MRB_ARGS_ARG(1,1)),
  MRB_MT_ENTRY(mrb_str_bytesplice,      MRB_SYM(bytesplice),      MRB_ARGS_ANY()),
  MRB_MT_ENTRY(sub_replace,             MRB_SYM(__sub_replace),   MRB_ARGS_REQ(3)),                   /* internal */
#ifndef MRB_NO_FLOAT
  MRB_MT_ENTRY(mrb_str_to_f,            MRB_SYM(to_f),            MRB_ARGS_NONE()),                   /* 15.2.10.5.38 */
#endif
};

void
mrb_init_string(mrb_state *mrb)
{
  struct RClass *s;

  mrb_static_assert(RSTRING_EMBED_LEN_MAX < (1 string_class = s = mrb_define_class_id(mrb, MRB_SYM(String), mrb->object_class);             /* 15.2.10 */
  MRB_SET_INSTANCE_TT(s, MRB_TT_STRING);

  MRB_MT_INIT_ROM(mrb, s, string_rom_entries);

  mrb_define_method_id(mrb, mrb->kernel_module, MRB_SYM(__ENCODING__), mrb_encoding, MRB_ARGS_NONE());
}

Web Proxy Viewer  |  New URL  |  Original Page