/* ** 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 <= 0x10FFFF) { buf[0] = (char)(0xF0 | (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 <= 3 && back <= p - beg; back++) { const char *lead = p - back; if (!utf8_islead(lead[0])) continue; /* another continuation byte */ return mrb_utf8len(lead, end) > back ? lead : p; } return p; } /* Decode a UTF-8 character and return its codepoint. *lenp is set to the byte length consumed. mrb_utf8len() answers 1 for every sequence it rejects, so those consume a single byte and come back as the lead byte itself. */ uint32_t mrb_utf8_decode(const char *p, const char *e, mrb_int *lenp) { uint8_t c = (uint8_t)p[0]; uint32_t cp; mrb_int n = mrb_utf8len(p, e); *lenp = n; switch (n) { case 2: cp = (c & 0x1f) << 6; cp |= ((uint8_t)p[1] & 0x3f); return cp; case 3: cp = (c & 0x0f) << 12; cp |= ((uint8_t)p[1] & 0x3f) << 6; cp |= ((uint8_t)p[2] & 0x3f); return cp; case 4: cp = (c & 0x07) << 18; cp |= ((uint8_t)p[1] & 0x3f) << 12; cp |= ((uint8_t)p[2] & 0x3f) << 6; cp |= ((uint8_t)p[3] & 0x3f); return cp; default: return c; /* ASCII, or invalid/truncated byte returned as-is */ } } #define NOASCII(c) ((c) & 0x80) #ifdef SIMPLE_SEARCH_NONASCII /* the naive implementation. define SIMPLE_SEARCH_NONASCII, */ /* if you need it for any constraint (e.g. code size). */ static const char* search_nonascii(const char* p, const char *e) { for (; p < e; ++p) { if (NOASCII(*p)) return p; } return e; } #elif defined(__SSE2__) # include static inline const char * search_nonascii(const char *p, const char *e) { if (sizeof(__m128i) < (size_t)(e - p)) { if (!_mm_movemask_epi8(_mm_loadu_si128((__m128i const*)p))) { const intptr_t lowbits = sizeof(__m128i) - 1; const __m128i *s, *t; s = (const __m128i*)(~lowbits & ((intptr_t)p + lowbits)); t = (const __m128i*)(~lowbits & (intptr_t)e); for (; s < t; ++s) { if (_mm_movemask_epi8(_mm_load_si128(s))) break; } p = (const char *)s; } } switch (e - p) { default: case 15: if (NOASCII(*p)) return p; ++p; case 14: if (NOASCII(*p)) return p; ++p; case 13: if (NOASCII(*p)) return p; ++p; case 12: if (NOASCII(*p)) return p; ++p; case 11: if (NOASCII(*p)) return p; ++p; case 10: if (NOASCII(*p)) return p; ++p; case 9: if (NOASCII(*p)) return p; ++p; case 8: if (NOASCII(*p)) return p; ++p; case 7: if (NOASCII(*p)) return p; ++p; case 6: if (NOASCII(*p)) return p; ++p; case 5: if (NOASCII(*p)) return p; ++p; case 4: if (NOASCII(*p)) return p; ++p; case 3: if (NOASCII(*p)) return p; ++p; case 2: if (NOASCII(*p)) return p; ++p; case 1: if (NOASCII(*p)) return p; ++p; if (NOASCII(*p)) return p; case 0: break; } return e; } #else static const char* search_nonascii(const char *p, const char *e) { ptrdiff_t byte_len = e - p; const char *be = p + sizeof(bitint) * (byte_len / sizeof(bitint)); for (; p < be; p+=sizeof(bitint)) { bitint t0; memcpy(&t0, p, sizeof(bitint)); const bitint t1 = t0 & (MASK01*0x80); if (t1) { e = p + sizeof(bitint)-1; byte_len = sizeof(bitint)-1; break; } } switch (byte_len % sizeof(bitint)) { #ifdef MRB_64BIT case 7: if (e[-7]&0x80) return e-7; case 6: if (e[-6]&0x80) return e-6; case 5: if (e[-5]&0x80) return e-5; case 4: if (e[-4]&0x80) return e-4; #endif case 3: if (e[-3]&0x80) return e-3; case 2: if (e[-2]&0x80) return e-2; case 1: if (e[-1]&0x80) return e-1; } return e; } #endif /* SIMPLE_SEARCH_NONASCII */ #if defined(__GNUC__) || __has_builtin(__builtin_popcount) # ifdef MRB_64BIT # define popcount(x) __builtin_popcountll(x) # else # define popcount(x) __builtin_popcountl(x) # endif #else #define POPC_SHIFT (8 * sizeof(bitint) - 8) static inline uint32_t popcount(bitint x) { x = (x & (MASK01*0x55)) + ((x >> 1) & (MASK01*0x55)); x = (x & (MASK01*0x33)) + ((x >> 2) & (MASK01*0x33)); x = (x & (MASK01*0x0F)) + ((x >> 4) & (MASK01*0x0F)); return (uint32_t)((x * MASK01) >> 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 <= byte_len); return utf8_len; } } /* whether a string's bytes read as the encoding it is taken to have */ mrb_bool mrb_str_valid_encoding_p(mrb_state *mrb, mrb_value str) { (void)mrb; struct RString *s = mrb_str_ptr(str); /* A byte-indexed string makes no such claim, so it is valid whatever its bytes are. */ if (RSTR_BINARY_P(s)) return TRUE; /* The walk below reads the whole string to answer either way, so a string that has been walked already is answered off where it stands instead. A string of one character per byte is one of those: it holds nothing but ASCII, and ASCII reads as UTF-8 as it stands. This is what a string counted before it is asked about comes in carrying. */ mrb_int cr = RSTR_CODERANGE(s); if (cr == MRB_STR_CODERANGE_7BIT || cr == MRB_STR_CODERANGE_VALID) return TRUE; if (cr == MRB_STR_CODERANGE_BROKEN) return FALSE; mrb_int byte_len = RSTR_LEN(s); mrb_bool valid = TRUE; mrb_int utf8_len = utf8_strlen_check(RSTR_PTR(s), byte_len, &valid); if (!valid) { RSTR_CODERANGE_SET(s, MRB_STR_CODERANGE_BROKEN); return FALSE; } RSTR_CODERANGE_SET(s, byte_len == utf8_len ? MRB_STR_CODERANGE_7BIT : MRB_STR_CODERANGE_VALID); return TRUE; } /* whether every byte of the string is ASCII. A walk that finds nothing else has made the statement 7BIT makes, so the answer is left on the string for the next asker to read off. */ static mrb_bool str_ascii_p(struct RString *s) { if (RSTR_CODERANGE(s) == MRB_STR_CODERANGE_7BIT) return TRUE; const char *p = RSTR_PTR(s); const char *e = p + RSTR_LEN(s); if (search_nonascii(p, e) != e) return FALSE; RSTR_CODERANGE_SET(s, MRB_STR_CODERANGE_7BIT); return TRUE; } /* Whether a character index into this string is already a byte index, asking the bytes where the string does not say. RSTR_SINGLE_BYTE_P() reads what is recorded and answers no for a string nothing has read yet, which sends every later caller down the walking path however plain the bytes are. A string is walked whole at most once here: the walk records what it finds, and it is the same walk the character indexing would go on to do anyway. */ mrb_bool mrb_str_single_byte_p(mrb_state *mrb, mrb_value str) { struct RString *s = mrb_str_ptr(str); if (RSTR_CODERANGE(s) == MRB_STR_CODERANGE_UNKNOWN) { mrb_str_valid_encoding_p(mrb, str); } return RSTR_SINGLE_BYTE_P(s); } /* map character index to byte offset index */ mrb_int mrb_str_char_to_byte(mrb_state *mrb, mrb_value str, mrb_int off, mrb_int idx) { (void)mrb; struct RString *s = mrb_str_ptr(str); if (RSTR_SINGLE_BYTE_P(s)) { return idx; } const char *o = RSTR_PTR(s); const char *p0 = o + off; const char *p = p0; const char *e = o + RSTR_LEN(s); mrb_int i = 0; while (p (idx - i) ? p + (idx - i) : e; const char *np = search_nonascii(p, lim); i += np - p; p = np; } else { p += mrb_utf8len(p, e); i++; } } mrb_int len = (mrb_int)(p-p0); if (i 0) { pos = mrb_str_char_to_byte(mrb, str, 0, pos); } pos = mrb_str_index(mrb, str, ptr, len, pos); if (pos > 0) { pos = mrb_str_byte_to_char(mrb, str, pos); } return pos; } #else /* a byte is a character here, so the count is the byte length and both conversions are identity */ mrb_int mrb_str_char_len(mrb_state *mrb, mrb_value str) { (void)mrb; return RSTRING_LEN(str); } mrb_int mrb_str_char_to_byte(mrb_state *mrb, mrb_value str, mrb_int off, mrb_int idx) { (void)mrb; (void)str; (void)off; return idx; } mrb_int mrb_str_byte_to_char(mrb_state *mrb, mrb_value str, mrb_int bi) { (void)mrb; if (bi < 0 || RSTRING_LEN(str) < bi) return -1; return bi; } #define str_index_str_by_char(mrb, str, sub, pos) str_index_str((mrb), (str), (sub), (pos)) /* a string is bytes here, with no encoding to disagree with */ mrb_bool mrb_str_valid_encoding_p(mrb_state *mrb, mrb_value str) { (void)mrb; (void)str; return TRUE; } #define str_ascii_p(s) TRUE #endif /* memsearch_swar (SWAR stands for SIMD within a register) */ /* See https://en.wikipedia.org/wiki/SWAR */ /* The function is taken from http://0x80.pl/articles/simd-strfind.html */ /* The original source code is under 2-clause BSD license; see LEGAL file. */ /* The modifications: * port from C++ to C * returns mrb_int * remove alignment issue * support bigendian CPU * fixed potential buffer overflow */ static inline mrb_int memsearch_swar(const char *xs, mrb_int m, const char *ys, mrb_int n) { #define MASK7f (MASK01*0x7f) #define MASK80 (MASK01*0x80) #if defined(MRB_ENDIAN_BIG) #ifdef MRB_64BIT #define MASKtop 0x8000000000000000ull #else #define MASKtop 0x80000000ul #endif #else #define MASKtop 0x80 #endif const bitint first = MASK01 * (uint8_t)xs[0]; const bitint last = MASK01 * (uint8_t)xs[m-1]; const char *s0 = ys; const char *s1 = ys+m-1; const mrb_int lim = n - m - (mrb_int)sizeof(bitint); mrb_int i; for (i=0; i < lim; i+=sizeof(bitint)) { bitint t0, t1; memcpy(&t0, s0+i, sizeof(bitint)); memcpy(&t1, s1+i, sizeof(bitint)); const bitint eq = (t0 ^ first) | (t1 ^ last); bitint zeros = ((~eq & MASK7f) + MASK01) & (~eq & MASK80); for (size_t j = 0; zeros; j++) { if (zeros & MASKtop) { const mrb_int idx = i + j; const char* p = s0 + idx + 1; if (memcmp(p, xs + 1, m - 2) == 0) { return idx; } } #if defined(MRB_ENDIAN_BIG) zeros <<= 8; #else zeros >>= 8; #endif } } if (i+m < n) { const char *p = s0; const char *e = ys + n; while (p n) return -1; else if (m == n) { return memcmp(x, y, m) == 0 ? 0 : -1; } else if (m < 1) { return 0; } else if (m == 1) { const char *p = (const char*)memchr(y, *x, n); if (p) return (mrb_int)(p - y); return -1; } return memsearch_swar(x, m, y, n); } static void str_share(mrb_state *mrb, struct RString *orig, struct RString *s) { size_t len = (size_t)orig->as.heap.len; mrb_assert(!RSTR_EMBED_P(orig)); if (RSTR_NOFREE_P(orig)) { str_init_nofree(s, orig->as.heap.ptr, len); } else if (RSTR_SHARED_P(orig)) { str_init_shared(mrb, orig, s, orig->as.heap.aux.shared); } else if (RSTR_FSHARED_P(orig)) { str_init_fshared(orig, s, orig->as.heap.aux.fshared); } else { /* Spare capacity is kept, not trimmed: it lies above `reserved`, so `orig` can still append into it without copying the buffer. */ str_init_shared(mrb, orig, s, NULL); str_init_shared(mrb, orig, orig, s->as.heap.aux.shared); } } /* * @param mrb The mruby state. * @param str The original mruby string. * @param beg The starting byte offset of the substring. * @param len The length in bytes of the substring. * @return A new mruby string representing the byte subsequence. * * Creates a new mruby string that is a subsequence of an existing string, * based on byte offsets and length. This function may share the underlying * buffer with the original string if possible. */ mrb_value mrb_str_byte_subseq(mrb_state *mrb, mrb_value str, mrb_int beg, mrb_int len) { struct RString *orig = mrb_str_ptr(str); struct RString *s = mrb_obj_alloc_string(mrb); if (RSTR_EMBEDDABLE_P(len)) { str_init_embed(s, RSTR_PTR(orig)+beg, len); } else { str_share(mrb, orig, s); s->as.heap.ptr += (mrb_ssize)beg; s->as.heap.len = (mrb_ssize)len; } RSTR_ENC_CR_COPY_FOR_SUBSTR(s, orig); return mrb_obj_value(s); } #ifdef MRB_UTF8_STRING static inline mrb_value str_subseq(mrb_state *mrb, mrb_value str, mrb_int beg, mrb_int len) { beg = mrb_str_char_to_byte(mrb, str, 0, beg); len = mrb_str_char_to_byte(mrb, str, beg, len); return mrb_str_byte_subseq(mrb, str, beg, len); } #else #define str_subseq(mrb, str, beg, len) mrb_str_byte_subseq(mrb, str, beg, len) #endif mrb_bool mrb_str_beg_len(mrb_int str_len, mrb_int *begp, mrb_int *lenp) { if (str_len < *begp || *lenp < 0) return FALSE; if (*begp < 0) { *begp += str_len; if (*begp < 0) return FALSE; } if (*lenp > str_len - *begp) *lenp = str_len - *begp; if (*lenp <= 0) { *lenp = 0; } return TRUE; } #ifdef MRB_UTF8_STRING /* What a substring needs of the string is where two positions are, not how many the string has. Counting the whole of it to find that out reads every byte however near the head the range sits, so the walk here stops at the range instead: forward to `beg` for a position counted from the head, and backward from the end for one counted from there. A position past the end is what the forward walk reports by coming back longer than the string, since mrb_str_char_to_byte() answers one byte more than it reached when the string ends before the index does. */ static mrb_value str_substr(mrb_state *mrb, mrb_value str, mrb_int beg, mrb_int len) { struct RString *s = mrb_str_ptr(str); mrb_int slen = RSTR_LEN(s); if (mrb_str_single_byte_p(mrb, str)) { return mrb_str_beg_len(slen, &beg, &len) ? mrb_str_byte_subseq(mrb, str, beg, len) : mrb_nil_value(); } if (len < 0) return mrb_nil_value(); const char *o = RSTR_PTR(s); mrb_int bbeg; if (beg < 0) { const char *e = o + slen; const char *p = e; for (mrb_int n = beg; n < 0; n++) { /* stepping back off the first character leaves the string, which is the negative index that names no position */ if (p == o) return mrb_nil_value(); p = mrb_utf8_char_head(o, p-1, e); } bbeg = (mrb_int)(p - o); } else { bbeg = mrb_str_char_to_byte(mrb, str, 0, beg); if (bbeg > slen) return mrb_nil_value(); } mrb_int blen = mrb_str_char_to_byte(mrb, str, bbeg, len); if (blen > slen - bbeg) blen = slen - bbeg; return mrb_str_byte_subseq(mrb, str, bbeg, blen); } #else static mrb_value str_substr(mrb_state *mrb, mrb_value str, mrb_int beg, mrb_int len) { return mrb_str_beg_len(mrb_str_char_len(mrb, str), &beg, &len) ? str_subseq(mrb, str, beg, len) : mrb_nil_value(); } #endif /* * @param mrb The mruby state. * @param str The mruby string to search in. * @param sptr A pointer to the C string to search for. * @param slen The length of the C string to search for. * @param offset The byte offset at which to start the search. * @return The byte offset of the first occurrence of the substring, or -1 if not found. * * Finds the first occurrence of a C string within an mruby string, starting from a given offset. * The search is performed on a byte-by-byte basis. */ MRB_API mrb_int mrb_str_index(mrb_state *mrb, mrb_value str, const char *sptr, mrb_int slen, mrb_int offset) { mrb_int len = RSTRING_LEN(str); if (offset < 0) { offset += len; if (offset < 0) return -1; } if (len - offset < slen) return -1; char *s = RSTRING_PTR(str); if (offset) { s += offset; } if (slen == 0) return offset; /* need proceed one character at a time */ len = RSTRING_LEN(str) - offset; mrb_int pos = mrb_memsearch(sptr, slen, s, len); if (pos < 0) return pos; return pos + offset; } static mrb_int str_index_str(mrb_state *mrb, mrb_value str, mrb_value str2, mrb_int offset) { /* A needle whose bytes are not the encoding it is taken to be in spells no character to look for, so it is found nowhere. CRuby answers the same way (is_broken_string in rb_str_index_m); a binary needle claims no encoding and is still searched for byte by byte. */ if (!mrb_str_valid_encoding_p(mrb, str2)) return -1; const char *ptr = RSTRING_PTR(str2); mrb_int len = RSTRING_LEN(str2); return mrb_str_index(mrb, str, ptr, len, offset); } static mrb_value str_replace(mrb_state *mrb, struct RString *s1, struct RString *s2) { mrb_check_frozen(mrb, s1); if (s1 == s2) return mrb_obj_value(s1); RSTR_ENC_CR_COPY(s1, s2); if (RSTR_SHARED_P(s1)) { str_decref(mrb, s1->as.heap.aux.shared); } else if (!RSTR_EMBED_P(s1) && !RSTR_NOFREE_P(s1) && !RSTR_FSHARED_P(s1)) { mrb_free(mrb, s1->as.heap.ptr); } size_t len = (size_t)RSTR_LEN(s2); if (RSTR_EMBEDDABLE_P(len)) { str_init_embed(s1, RSTR_PTR(s2), len); } else { str_share(mrb, s2, s1); } return mrb_obj_value(s1); } /* Search backward for `sub` one byte at a time, the mirror of the forward scan in str_index(). This is what a byte-indexed string answers with. */ static mrb_int str_byterindex(mrb_value str, mrb_value sub, mrb_int pos) { const char *sbeg, *t; struct RString *ps = mrb_str_ptr(str); mrb_int len = RSTRING_LEN(sub); mrb_int slen = RSTR_LEN(ps); /* substring longer than string */ if (slen < len) return -1; if (slen - pos < len) { pos = slen - len; } if (len == 0) return pos; sbeg = RSTR_PTR(ps); t = RSTRING_PTR(sub); /* The first byte has to match wherever the rest does, and comparing it here settles all but the positions that carry it. Handing every position to memcmp() instead pays for a call at each one, which is what the search spends nearly all of its time on where the needle is not there to find. */ const char head = t[0]; /* count down an index rather than a pointer: stepping a pointer past the first byte to end the search would leave the buffer */ for (mrb_int i = pos; 0 <= i; i--) { if (sbeg[i] == head && memcmp(sbeg+i, t, len) == 0) { return i; } } return -1; } #ifdef MRB_UTF8_STRING /* Search backward for `sub` over character boundaries, so a match starting inside a multi-byte character is stepped over rather than reported. This is what a character-indexed string answers with. */ static mrb_int str_char_rindex(mrb_value str, mrb_value sub, mrb_int pos) { const char *s, *sbeg, *send, *t; struct RString *ps = mrb_str_ptr(str); mrb_int len = RSTRING_LEN(sub); mrb_int slen = RSTR_LEN(ps); /* substring longer than string */ if (slen < len) return -1; if (slen - pos < len) { pos = slen - len; } sbeg = RSTR_PTR(ps); send = sbeg + slen; s = sbeg + pos; t = RSTRING_PTR(sub); if (len) { /* a match may start only at a character boundary, and `pos` need not be one: the clamp above answers the last byte `sub` fits at */ s = mrb_utf8_char_head(sbeg, s, send); /* see str_byterindex(): the first byte settles all but the positions carrying it, and it is read here anyway to step back from */ const char head = t[0]; for (;;) { if (*s == head && (mrb_int)(send - s) >= len && memcmp(s, t, len) == 0) { return (mrb_int)(s - sbeg); } /* the character before `s`, which there is none of once the search has reached the first one */ if (s == sbeg) break; s = mrb_utf8_char_head(sbeg, s-1, send); } return -1; } else { return pos; } } #endif #ifdef _WIN32 #include #include #include /* The conversion pair is written once and the allocator is what varies: `mrb` is NULL for the public functions, which allocate with malloc() and answer a refusal with -1, and non-NULL for the mrb_malloc() variants, where a failed allocation raises instead. Everything else -- the length conventions, the terminator, the -1 the code page answers a byte it cannot read with -- is the same question either way, so it is asked in one place. */ static void* w32_conv_alloc(mrb_state *mrb, size_t size) { if (mrb) return mrb_malloc(mrb, size); return malloc(size); } static void w32_conv_free(mrb_state *mrb, void *p) { if (mrb) mrb_free(mrb, p); else free(p); } static int w32_mbs_to_wcs(mrb_state *mrb, const char *mbsp, int len, wchar_t **wcsp, uint32_t from_cp, uint32_t flags) { wchar_t *buf; int need; int written; if (wcsp == NULL) return -1; *wcsp = NULL; /* Keep the output NULL unless conversion succeeds. */ if (mbsp == NULL || len < -1) return -1; if (len == -1) { size_t n = strlen(mbsp); if (n > INT_MAX) return -1; len = (int)n; } if (len == 0) { buf = (wchar_t*)w32_conv_alloc(mrb, sizeof(wchar_t)); if (buf == NULL) return -1; buf[0] = L'\0'; *wcsp = buf; return 0; } need = MultiByteToWideChar(from_cp, flags, mbsp, len, NULL, 0); if (need <= 0 || (size_t)need >= SIZE_MAX / sizeof(wchar_t)) return -1; buf = (wchar_t*)w32_conv_alloc(mrb, ((size_t)need + 1) * sizeof(wchar_t)); if (buf == NULL) return -1; written = MultiByteToWideChar(from_cp, flags, mbsp, len, buf, need); if (written <= 0) { w32_conv_free(mrb, buf); return -1; } buf[written] = L'\0'; *wcsp = buf; return written; } static int w32_wcs_to_mbs(mrb_state *mrb, const wchar_t *wcsp, int len, char **mbsp, uint32_t to_cp, uint32_t flags) { char *buf; int need; int written; if (mbsp == NULL) return -1; *mbsp = NULL; /* Keep the output NULL unless conversion succeeds. */ if (wcsp == NULL || len < -1) return -1; if (len == -1) { size_t n = wcslen(wcsp); if (n > INT_MAX) return -1; len = (int)n; } if (len == 0) { buf = (char*)w32_conv_alloc(mrb, 1); if (buf == NULL) return -1; buf[0] = '\0'; *mbsp = buf; return 0; } need = WideCharToMultiByte(to_cp, flags, wcsp, len, NULL, 0, NULL, NULL); if (need <= 0) return -1; buf = (char*)w32_conv_alloc(mrb, (size_t)need + 1); if (buf == NULL) return -1; written = WideCharToMultiByte(to_cp, flags, wcsp, len, buf, need, NULL, NULL); if (written <= 0) { w32_conv_free(mrb, buf); return -1; } buf[written] = '\0'; *mbsp = buf; return written; } MRB_API int mrb_mbs_to_wcs(const char *mbsp, int len, wchar_t **wcsp, uint32_t from_cp, uint32_t flags) { return w32_mbs_to_wcs(NULL, mbsp, len, wcsp, from_cp, flags); } MRB_API int mrb_wcs_to_mbs(const wchar_t *wcsp, int len, char **mbsp, uint32_t to_cp, uint32_t flags) { return w32_wcs_to_mbs(NULL, wcsp, len, mbsp, to_cp, flags); } int mrb_mbs_to_wcs_m(mrb_state *mrb, const char *mbsp, int len, wchar_t **wcsp, uint32_t from_cp, uint32_t flags) { return w32_mbs_to_wcs(mrb, mbsp, len, wcsp, from_cp, flags); } int mrb_wcs_to_mbs_m(mrb_state *mrb, const wchar_t *wcsp, int len, char **mbsp, uint32_t to_cp, uint32_t flags) { return w32_wcs_to_mbs(mrb, wcsp, len, mbsp, to_cp, flags); } MRB_API char* mrb_utf8_from_locale(const char *str, int len) { wchar_t *wcsp; char *mbsp; int wcssize; if (len == 0) return strdup(""); wcssize = mrb_mbs_to_wcs(str, len, &wcsp, GetACP(), 0); if (wcssize < 0) return NULL; if (mrb_wcs_to_mbs(wcsp, wcssize, &mbsp, CP_UTF8, 0) < 0) { free(wcsp); return NULL; } free(wcsp); return mbsp; } MRB_API char* mrb_locale_from_utf8(const char *utf8, int len) { wchar_t *wcsp; char *mbsp; int wcssize; if (len == 0) return strdup(""); wcssize = mrb_mbs_to_wcs(utf8, len, &wcsp, CP_UTF8, 0); if (wcssize < 0) return NULL; if (mrb_wcs_to_mbs(wcsp, wcssize, &mbsp, GetACP(), 0) < 0) { free(wcsp); return NULL; } free(wcsp); return mbsp; } #endif /* * @param mrb The mruby state. * @param s The RString structure to modify. * * Prepares a string for modification. If the string is shared or not extensible, * it will be unshared or converted to a normal string. What the bytes were read * as stops holding here, so this is the prepare for a write that can change it. * Raises an error if the string is frozen. */ MRB_API void mrb_str_modify(mrb_state *mrb, struct RString *s) { mrb_check_frozen(mrb, s); str_unshare_buffer(mrb, s); RSTR_CODERANGE_SET(s, MRB_STR_CODERANGE_UNKNOWN); } /* mrb_str_modify() for a caller whose write leaves what the bytes read as standing: it puts ASCII where ASCII stood, or it cuts where a character ends. Such a write cannot turn a sound string unsound, so the answer the string came in carrying is still the answer, and the next asker is spared the walk that would arrive at it again. Only a string already read as broken has to be asked again, since a write is as likely to have mended it as to have left it broken. A string that the write leaves holding nothing but ASCII keeps saying VALID rather than moving to 7BIT: that is an answer worth less than the truth, not a wrong one, and finding the truth is the walk this is here to skip. The promise this asks of its caller cannot be checked here, which is why it is not offered outside the library. */ static void str_modify_keep_cr(mrb_state *mrb, struct RString *s) { mrb_check_frozen(mrb, s); str_unshare_buffer(mrb, s); if (RSTR_CODERANGE(s) == MRB_STR_CODERANGE_BROKEN) { RSTR_CODERANGE_SET(s, MRB_STR_CODERANGE_UNKNOWN); } } /* * @param mrb The mruby state. * @param str The mruby string to resize. * @param len The new desired length of the string. * @return The resized mruby string. * * Resizes an mruby string to a new length. * If the new length is shorter, the string is truncated. * If the new length is longer, the string is extended, and the new portion's * content is undefined (it might be null bytes or garbage). * The string is modified in place. */ MRB_API mrb_value mrb_str_resize(mrb_state *mrb, mrb_value str, mrb_int len) { struct RString *s = mrb_str_ptr(str); str_check_length(mrb, len); mrb_str_modify(mrb, s); mrb_int slen = RSTR_LEN(s); if (len != slen) { if (slen < len || slen - 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 <= len/2) { memcpy(p + n, p, n); n *= 2; } memcpy(p + n, p, len-n); } p[RSTR_LEN(str2)] = '\0'; /* A repetition holds the receiver's bytes over again, so it is read the same way and reaches the same broken place the first copy does. */ RSTR_ENC_CR_COPY(str2, mrb_str_ptr(self)); /* Nought copies keep none of the bytes, and an empty string is not broken whatever it was made from. */ if (len == 0 && RSTR_CODERANGE(str2) == MRB_STR_CODERANGE_BROKEN) { RSTR_CODERANGE_SET(str2, MRB_STR_CODERANGE_UNKNOWN); } return mrb_obj_value(str2); } /* -------------------------------------------------------------- */ #define lesser(a,b) (((a)>(b))?(b):(a)) /* ---------------------------*/ /* * call-seq: * mrb_value str1 <=> mrb_value str2 => int * > 1 * = 0 * < -1 */ /* * @param mrb The mruby state. * @param str1 The first mruby string for comparison. * @param str2 The second mruby string for comparison (must be a string). * @return An integer less than, equal to, or greater than zero if `str1` is less than, * equal to, or greater than `str2`, respectively. * * Compares two mruby strings lexicographically. * Assumes `str2` is already a string. For a version that checks and converts, see `mrb_str_cmp_m`. */ MRB_API int mrb_str_cmp(mrb_state *mrb, mrb_value str1, mrb_value str2) { struct RString *s1 = mrb_str_ptr(str1); struct RString *s2 = mrb_str_ptr(str2); mrb_int len1 = RSTR_LEN(s1); mrb_int len2 = RSTR_LEN(s2); mrb_int len = lesser(len1, len2); mrb_int retval = (len == 0) ? 0 : memcmp(RSTR_PTR(s1), RSTR_PTR(s2), len); if (retval == 0) { if (len1 == len2) return 0; if (len1 > len2) return 1; return -1; } if (retval > 0) return 1; return -1; } #undef lesser /* 15.2.10.5.3 */ /* * call-seq: * str <=> other_str => -1, 0, +1 * * Comparison---Returns -1 if `other_str` is less than, 0 if * `other_str` is equal to, and +1 if `other_str` is greater than * `str`. If the strings are of different lengths, and the strings are * equal when compared up to the shortest length, then the longer string is * considered greater than the shorter one. If the variable `$=` is * `false`, the comparison is based on comparing the binary values * of each character in the string. In older versions of Ruby, setting * `$=` allowed case-insensitive comparisons; this is now deprecated * in favor of using `String#casecmp`. * * `<=>` is the basis for the methods `<`, `<=`, `>`, `>=`, and `between?`, * included from module `Comparable`. The method `String#==` does not use * `Comparable#==`. * * "abcdef" <=> "abcde" #=> 1 * "abcdef" <=> "abcdef" #=> 0 * "abcdef" <=> "abcdefg" #=> -1 * "abcdef" <=> "ABCDEF" #=> 1 */ static mrb_value mrb_str_cmp_m(mrb_state *mrb, mrb_value str1) { mrb_value str2 = mrb_get_arg1(mrb); if (!mrb_string_p(str2)) { return mrb_nil_value(); } mrb_int result = mrb_str_cmp(mrb, str1, str2); return mrb_int_value(mrb, result); } static mrb_bool str_eql(mrb_state *mrb, const mrb_value str1, const mrb_value str2) { const mrb_int len = RSTRING_LEN(str1); if (len != RSTRING_LEN(str2)) return FALSE; return (memcmp(RSTRING_PTR(str1), RSTRING_PTR(str2), (size_t)len) == 0); } /* * @param mrb The mruby state. * @param str1 The first mruby string. * @param str2 The second mruby value to compare with. * @return `TRUE` if `str1` and `str2` are equal strings, `FALSE` otherwise. * * Checks if two mruby strings are equal. * Returns `FALSE` if `str2` is not a string. */ MRB_API mrb_bool mrb_str_equal(mrb_state *mrb, mrb_value str1, mrb_value str2) { if (!mrb_string_p(str2)) return FALSE; return str_eql(mrb, str1, str2); } /* 15.2.10.5.4 */ /* * call-seq: * str == obj => true or false * * Equality--- * If `obj` is not a `String`, returns `false`. * Otherwise, returns `false` or `true` * * caution:if `str` `<=>` `obj` returns zero. */ static mrb_value mrb_str_equal_m(mrb_state *mrb, mrb_value str1) { mrb_value str2 = mrb_get_arg1(mrb); return mrb_bool_value(mrb_str_equal(mrb, str1, str2)); } /* ---------------------------------- */ /* * @param mrb The mruby state. * @param str The mruby string to duplicate. * @return A new mruby string that is a copy of the original. * * Creates a new mruby string that is a duplicate of the given string. * The new string will have its own buffer. */ MRB_API mrb_value mrb_str_dup(mrb_state *mrb, mrb_value str) { struct RString *s = mrb_str_ptr(str); struct RString *dup = str_new(mrb, 0, 0); return str_replace(mrb, dup, s); } MRB_API mrb_value mrb_str_dup_frozen(mrb_state *mrb, mrb_value str) { if (!mrb_frozen_p(mrb_basic_ptr(str))) { str = mrb_str_dup(mrb, str); mrb_basic_ptr(str)->frozen = TRUE; } return str; } enum str_convert_range { /* `beg` and `len` are byte unit in `0 ... str.bytesize` */ STR_BYTE_RANGE_CORRECTED = 1, /* `beg` and `len` are char unit in any range */ STR_CHAR_RANGE = 2, /* `beg` and `len` are char unit in `0 ... str.size` */ STR_CHAR_RANGE_CORRECTED = 3, /* `beg` is out of range */ STR_OUT_OF_RANGE = -1 }; static enum str_convert_range str_convert_range(mrb_state *mrb, mrb_value str, mrb_value idx, mrb_value alen, mrb_int *beg, mrb_int *len) { if (!mrb_undef_p(alen)) { *beg = mrb_as_int(mrb, idx); *len = mrb_as_int(mrb, alen); return STR_CHAR_RANGE; } else { switch (mrb_type(idx)) { default: idx = mrb_ensure_int_type(mrb, idx); /* fall through */ case MRB_TT_INTEGER: *beg = mrb_integer(idx); *len = 1; return STR_CHAR_RANGE; case MRB_TT_STRING: *beg = str_index_str(mrb, str, idx, 0); if (*beg < 0) { break; } *len = RSTRING_LEN(idx); return STR_BYTE_RANGE_CORRECTED; case MRB_TT_RANGE: *len = mrb_str_char_len(mrb, str); switch (mrb_range_beg_len(mrb, idx, beg, len, *len, TRUE)) { case MRB_RANGE_OK: return STR_CHAR_RANGE_CORRECTED; case MRB_RANGE_OUT: return STR_OUT_OF_RANGE; default: break; } } } return STR_OUT_OF_RANGE; } /* * @param mrb The mruby state. * @param str The mruby string. * @param idx The index or range. Can be an integer, a string, or a range. * @param alen An optional length (if `idx` is an integer). * @return A new mruby string (substring), or nil if out of bounds or not found. * * Implements string element reference (e.g., `str[idx]`, `str[idx, len]`). * - If `idx` is an Integer, returns a substring of 1 character at that index (or `len` characters if `alen` is provided). * - If `idx` is a String, returns that string if it's a substring of `str`. * - If `idx` is a Range, returns the substring specified by the range. * Character indexing is used if UTF-8 is enabled, otherwise byte indexing. */ mrb_value mrb_str_aref(mrb_state *mrb, mrb_value str, mrb_value idx, mrb_value alen) { mrb_int beg, len; switch (str_convert_range(mrb, str, idx, alen, &beg, &len)) { case STR_CHAR_RANGE_CORRECTED: return str_subseq(mrb, str, beg, len); case STR_CHAR_RANGE: str = str_substr(mrb, str, beg, len); if (mrb_undef_p(alen) && !mrb_nil_p(str) && RSTRING_LEN(str) == 0) return mrb_nil_value(); return str; case STR_BYTE_RANGE_CORRECTED: if (mrb_string_p(idx)) { return mrb_str_dup(mrb, idx); } else { return mrb_str_byte_subseq(mrb, str, beg, len); } case STR_OUT_OF_RANGE: default: return mrb_nil_value(); } } /* 15.2.10.5.6 */ /* 15.2.10.5.34 */ /* * call-seq: * str[int] => 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); } 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"); } 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); /* 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 */ struct RString *repp = mrb_str_ptr(rep); if (!RSTR_BINARY_P(str) && RSTR_BINARY_P(repp) && !str_ascii_p(repp)) { RSTR_ENCODING_SET(str, MRB_STR_ENCODING_BINARY); } } 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; 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); 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 ("dz" to "Dz"). * * "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); 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 <= 1 || memcmp(RSTRING_PTR(rs), pp, rslen) == 0)) { #ifdef MRB_UTF8_STRING /* The bytes line up, but they can be the tail of a character rather than a character of its own, and cutting there would leave a string that is not UTF-8: "あ".chomp("\x82") is the whole of the last byte of a three-byte character. CRuby reads that as no match. */ if (!RSTR_SINGLE_BYTE_P(s) && mrb_utf8_char_head(p, pp, p + len) != pp) { return mrb_nil_value(); } /* Cutting bytes that are nothing but ASCII leaves what the rest is read as standing, non-ASCII and all, so the coderange str_modify_keep_cr() kept is still the answer. Cutting a non-ASCII byte can have taken the last of them, and a string of nothing but ASCII stands at 7BIT rather than VALID: what it is has to be asked again. */ if (search_nonascii(pp, pp + rslen) != pp + rslen) { RSTR_CODERANGE_SET(s, MRB_STR_CODERANGE_UNKNOWN); } #endif RSTR_SET_LEN(s, len - rslen); p[RSTR_LEN(s)] = '\0'; return str; } return mrb_nil_value(); } /* 15.2.10.5.9 */ /* * call-seq: * str.chomp(separator="\n") => 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); 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); 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<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24); #endif } /* return our new hash value */ return hval; } uint32_t mrb_byte_hash(const uint8_t *s, mrb_int len) { return mrb_byte_hash_step(s, len, FNV1_32_INIT); } uint32_t mrb_str_hash(mrb_state *mrb, mrb_value str) { struct RString *s = mrb_str_ptr(str); return mrb_byte_hash((uint8_t*)RSTR_PTR(s), RSTR_LEN(s)); } /* 15.2.10.5.20 */ /* * call-seq: * str.hash => int * * Return a hash based on the string's length and content. */ static mrb_value mrb_str_hash_m(mrb_state *mrb, mrb_value self) { mrb_int key = mrb_str_hash(mrb, self); return mrb_int_value(mrb, key); } /* 15.2.10.5.21 */ /* * call-seq: * str.include? other_str => true or false * str.include? int => true or false * * Returns `true` if *str* contains the given string or * character. * * "hello".include? "lo" #=> true * "hello".include? "ol" #=> false * "hello".include? ?h #=> true */ static mrb_value mrb_str_include(mrb_state *mrb, mrb_value self) { mrb_value str2; mrb_get_args(mrb, "S", &str2); if (str_index_str(mrb, self, str2, 0) < 0) return mrb_bool_value(FALSE); return mrb_bool_value(TRUE); } /* * call-seq: * str.byteindex(substring, offset = 0) -> integer or nil * * Returns the \Integer byte-based index of the first occurrence of the given `substring`, * or `nil` if none found: * * 'foo'.byteindex('f') # => 0 * 'foo'.byteindex('oo') # => 1 * 'foo'.byteindex('ooo') # => nil */ /* A byte offset that lands inside a character names no position the string has, so a byte search refuses it rather than starting from the middle of one. A byte-indexed string has a position per byte, so every offset is one. The boundaries are the ones String#length counts over, which is why a byte no lead byte reaches is one of them. */ void mrb_str_check_byte_pos(mrb_state *mrb, mrb_value str, mrb_int pos) { #ifdef MRB_UTF8_STRING struct RString *s = mrb_str_ptr(str); if (RSTR_SINGLE_BYTE_P(s)) return; const char *b = RSTR_PTR(s); const char *p = b + pos; if (mrb_utf8_char_head(b, p, b + RSTR_LEN(s)) != p) { mrb_raisef(mrb, E_INDEX_ERROR, "offset %i does not land on character boundary", pos); } #endif } static mrb_value mrb_str_byteindex_m(mrb_state *mrb, mrb_value str) { mrb_value sub; mrb_int pos; if (mrb_get_args(mrb, "S|i", &sub, &pos) == 1) { pos = 0; } else if (pos < 0) { pos += RSTRING_LEN(str); if (pos < 0) { return mrb_nil_value(); } } if (pos > RSTRING_LEN(str)) return mrb_nil_value(); 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_index_str(mrb, str, sub, pos); if (pos == -1) return mrb_nil_value(); return mrb_int_value(mrb, pos); } /* 15.2.10.5.22 */ /* * call-seq: * str.index(substring [, offset]) => int or nil * * Returns the index of the first occurrence of the given * *substring*. Returns `nil` if not found. * If the second parameter is present, it * specifies the position in the string to begin the search. * * "hello".index('l') #=> 2 * "hello".index('lo') #=> 3 * "hello".index('a') #=> nil * "hello".index('l', -2) #=> 3 */ #ifdef MRB_UTF8_STRING static mrb_value mrb_str_index_m(mrb_state *mrb, mrb_value str) { if (mrb_str_single_byte_p(mrb, str)) { return mrb_str_byteindex_m(mrb, str); } mrb_value sub; mrb_int pos; if (mrb_get_args(mrb, "S|i", &sub, &pos) == 1) { pos = 0; } else if (pos < 0) { mrb_int clen = mrb_str_char_len(mrb, str); pos += clen; if (pos < 0) { return mrb_nil_value(); } } pos = str_index_str_by_char(mrb, str, sub, pos); if (pos == -1) return mrb_nil_value(); return mrb_int_value(mrb, pos); } #else #define mrb_str_index_m mrb_str_byteindex_m #endif /* 15.2.10.5.24 */ /* 15.2.10.5.28 */ /* * call-seq: * str.replace(other_str) => str * * s = "hello" #=> "hello" * s.replace "world" #=> "world" */ static mrb_value mrb_str_replace(mrb_state *mrb, mrb_value str) { mrb_value str2; mrb_get_args(mrb, "S", &str2); return str_replace(mrb, mrb_str_ptr(str), mrb_str_ptr(str2)); } /* 15.2.10.5.23 */ /* * call-seq: * String.new(str="") => new_str * * Returns a new string object containing a copy of *str*. */ static mrb_value mrb_str_init(mrb_state *mrb, mrb_value self) { mrb_value str2; if (mrb_get_args(mrb, "|S", &str2) == 0) { str2 = mrb_str_new(mrb, 0, 0); } str_replace(mrb, mrb_str_ptr(self), mrb_str_ptr(str2)); return self; } /* 15.2.10.5.25 */ /* 15.2.10.5.41 */ /* * call-seq: * str.intern => symbol * str.to_sym => symbol * * Returns the `Symbol` corresponding to *str*, creating the * symbol if it did not previously exist. * * "Koala".intern #=> :Koala * s = 'cat'.to_sym #=> :cat * s == :cat #=> true * s = '@cat'.to_sym #=> :@cat * s == :@cat #=> true * * This can also be used to create symbols that cannot be represented using the * `:xxx` notation. * * 'cat and dog'.to_sym #=> :"cat and dog" */ /* * @param mrb The mruby state. * @param self The mruby string to convert to a symbol. * @return The mruby symbol corresponding to the string. * * Converts a mruby string to a symbol. If the symbol does not exist, it is created. */ MRB_API mrb_value mrb_str_intern(mrb_state *mrb, mrb_value self) { return mrb_symbol_value(mrb_intern_str(mrb, self)); } /* ---------------------------------- */ /* * @param mrb The mruby state. * @param obj The mruby value to convert to a string. * @return The string representation of the mruby value. * * Converts any mruby object to its string representation. * For strings, it returns the object itself. * For symbols, it returns the symbol's name as a string. * For integers, it converts the integer to a string (base 10). * For classes/modules, it returns their name. * For other types, it calls the `to_s` method on the object. */ MRB_API mrb_value mrb_obj_as_string(mrb_state *mrb, mrb_value obj) { switch (mrb_type(obj)) { case MRB_TT_STRING: return obj; case MRB_TT_SYMBOL: return mrb_sym_str(mrb, mrb_symbol(obj)); case MRB_TT_INTEGER: return mrb_integer_to_str(mrb, obj, 10); case MRB_TT_SCLASS: case MRB_TT_CLASS: case MRB_TT_MODULE: return mrb_mod_to_s(mrb, obj); default: return mrb_type_convert(mrb, obj, MRB_TT_STRING, MRB_SYM(to_s)); } } /* * @param mrb The mruby state. * @param p The pointer to convert. * @return A new mruby string representing the pointer address. * * Converts a C pointer to a mruby string representation (e.g., "0x..."). */ MRB_API mrb_value mrb_ptr_to_str(mrb_state *mrb, void *p) { struct RString *p_str; char *p1; char *p2; uintptr_t n = (uintptr_t)p; p_str = str_new(mrb, NULL, 2 + sizeof(uintptr_t) * CHAR_BIT / 4); p1 = RSTR_PTR(p_str); *p1++ = '0'; *p1++ = 'x'; p2 = p1; do { *p2++ = mrb_digitmap[n % 16]; n /= 16; } while (n > 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 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) return str; if (utf8_len < len) { str_modify_keep_cr(mrb, s); p = RSTR_PTR(s); e = p + RSTR_LEN(s); while (p 1) { str_modify_keep_cr(mrb, s); goto bytes; } 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 <= i) break; } } else if (ISSPACE(c)) { mrb_ary_push(mrb, result, mrb_str_byte_subseq(mrb, str, beg, end-beg)); mrb_gc_arena_restore(mrb, ai); skip = TRUE; beg = idx; if (lim_p) i++; } else { end = idx; } } } else { /* split_type == string */ mrb_int str_len = RSTRING_LEN(str); mrb_int pat_len = RSTRING_LEN(spat); mrb_int idx = 0; int ai = mrb_gc_arena_save(mrb); while (idx < str_len) { mrb_int end; if (pat_len > 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 <= ++i) break; } beg = idx; } if (RSTRING_LEN(str) > 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=pend) { if (badcheck) goto bad; return mrb_fixnum_value(0); } if (*p == '0') { /* squeeze preceding 0s */ p++; while (p= base) { break; } if (mrb_int_mul_overflow(n, base, &n)) goto overflow; if (MRB_INT_MAX - c < n) { if (sign == 0 && MRB_INT_MAX - n == c - 1) { n = MRB_INT_MIN; sign = 1; break; } overflow: #ifdef MRB_USE_BIGINT ; const char *p3 = p2; while (p3 < pend) { char c = TOLOWER(*p3); const char *p4 = strchr(mrb_digitmap, c); if (p4 == NULL && c != '_') break; if (p4 - mrb_digitmap >= base) break; p3++; } if (badcheck && trailingbad(str, p, pend)) goto bad; return mrb_bint_new_str(mrb, p2, (mrb_int)(p3-p2), sign ? base : -base); #else mrb_raisef(mrb, E_RANGE_ERROR, "string (%l) too big for integer", str, pend-str); #endif } n += c; } val = (mrb_int)n; if (badcheck && trailingbad(str, p, pend)) goto bad; return mrb_int_value(mrb, sign ? val : -val); bad: mrb_raisef(mrb, E_ARGUMENT_ERROR, "invalid string for number(%!l)", str, pend-str); /* not reached */ return mrb_fixnum_value(0); } /* obsolete: use RSTRING_CSTR() or mrb_string_cstr() */ /* * @deprecated Use `RSTRING_CSTR()` or `mrb_string_cstr()` instead. * @param mrb The mruby state. * @param ptr Pointer to the mruby string value. * @return A pointer to a null-terminated C string. * * Ensures the mruby string pointed to by `ptr` is a string and returns its * C string representation. If the string contains null bytes, it raises an * E_ARGUMENT_ERROR. If the string is not null-terminated, it modifies the * string in place to add a null terminator (this might involve unsharing * the string buffer). */ MRB_API const char* mrb_string_value_cstr(mrb_state *mrb, mrb_value *ptr) { struct RString *ps; const char *p; mrb_int len; mrb_ensure_string_type(mrb, *ptr); ps = mrb_str_ptr(*ptr); check_null_byte(mrb, ps); p = RSTR_PTR(ps); len = RSTR_LEN(ps); if (p == NULL) return ""; if (p[len] == '\0') { return p; } /* * Even after mrb_str_modify(), NULL termination is not ensured if * RSTR_SET_LEN() is used explicitly (e.g. String#delete_suffix!). */ str_unshare_buffer(mrb, ps); RSTR_PTR(ps)[len] = '\0'; return RSTR_PTR(ps); } /* * @param mrb The mruby state. * @param str The mruby string value. * @return A pointer to a null-terminated C string. * * Ensures the mruby string `str` is a string and returns its C string representation. * This is a convenience wrapper around `mrb_string_value_cstr`. * If the string contains null bytes, it raises an E_ARGUMENT_ERROR. * If the string is not null-terminated, it modifies the string in place * to add a null terminator. */ MRB_API const char* mrb_string_cstr(mrb_state *mrb, mrb_value str) { return mrb_string_value_cstr(mrb, &str); } /* * @param mrb The mruby state. * @param str The mruby string to convert. * @param base The base for conversion (0 or 2-36). * @param badcheck If `TRUE`, raise an error on invalid input; otherwise, return 0. * @return An mruby integer value. * * Converts an mruby string to an mruby integer. * Interprets leading characters in `str` as an integer of the specified `base`. * If `base` is 0, it auto-detects the base (0x for hex, 0b for binary, 0o or 0 for octal, else decimal). * If `badcheck` is true, invalid characters will raise an `E_ARGUMENT_ERROR`. * Otherwise, extraneous characters are ignored, and 0 is returned for invalid numbers. */ MRB_API mrb_value mrb_str_to_integer(mrb_state *mrb, mrb_value str, mrb_int base, mrb_bool badcheck) { mrb_ensure_string_type(mrb, str); const char *s = RSTRING_PTR(str); mrb_int len = RSTRING_LEN(str); return mrb_str_len_to_integer(mrb, s, len, base, badcheck); } /* 15.2.10.5.38 */ /* * call-seq: * str.to_i(base=10) => integer * * Returns the result of interpreting leading characters in *str* as an * integer base *base* (between 2 and 36). Extraneous characters past the * end of a valid number are ignored. If there is not a valid number at the * start of *str*, `0` is returned. This method never raises an * exception. * * "12345".to_i #=> 12345 * "99 red balloons".to_i #=> 99 * "0a".to_i #=> 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 float * * Returns the result of interpreting leading characters in *str* as a * floating-point number. Extraneous characters past the end of a valid number * are ignored. If there is not a valid number at the start of *str*, * `0.0` is returned. This method never raises an exception. * * "123.45e1".to_f #=> 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; 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; if (len == 0) return str; /* `len` has to be known to fit in an `mrb_int` before it is used as one: the conversion is otherwise free to make it negative, and the overflow check takes `mrb_int` parameters, so it would not see it. Checking ahead of the modification also leaves the string untouched when it raises. */ mrb_int total; if (len > (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 <= str_addr + (uintptr_t)RSTR_LEN(s)) { off = (ptrdiff_t)(ptr_addr - str_addr); } /* Read before the modify below, which forgets it. */ uint32_t cr = RSTR_CODERANGE(s); mrb_int capa = str_modify_cat(mrb, s, (mrb_int)len); if (capa <= total) { if (capa == 0) capa = 1; while (capa <= total) { if (mrb_int_mul_overflow(capa, 2, &capa)) goto size_error; } resize_capa(mrb, s, capa); } if (off != -1) { ptr = RSTR_PTR(s) + off; } memcpy(RSTR_PTR(s) + RSTR_LEN(s), ptr, len); RSTR_SET_LEN(s, total); RSTR_PTR(s)[total] = '\0'; /* sentinel */ #ifdef MRB_UTF8_STRING /* An append is the one write that can say what the string stands at afterwards without reading it: ASCII bytes added to a string of nothing but ASCII leave a string of nothing but ASCII. Carrying that across is what keeps a loop of `buf << "..."` from walking the whole of `buf` again on the next question about its bytes, which is how appending in a loop and matching in the same loop came to take quadratic time. Only this pair is carried. VALID would need the appended bytes read as characters rather than scanned for the high bit, and the boundary between the two parts read as well, which is the walk this is avoiding. */ if (cr == MRB_STR_CODERANGE_7BIT && search_nonascii(ptr, ptr + len) == ptr + len) { RSTR_CODERANGE_SET(s, MRB_STR_CODERANGE_7BIT); } #else (void)cr; #endif return str; } /* * @param mrb The mruby state. * @param str The mruby string to append to (modified in place). * @param ptr A pointer to the null-terminated C string to append. * @return The modified mruby string `str`. * * Appends a null-terminated C string to an mruby string. * The mruby string `str` is modified in place. */ MRB_API mrb_value mrb_str_cat_cstr(mrb_state *mrb, mrb_value str, const char *ptr) { return mrb_str_cat(mrb, str, ptr, ptr ? strlen(ptr) : 0); } /* * @param mrb The mruby state. * @param str The mruby string to append to (modified in place). * @param str2 The mruby string to append. * @return The modified mruby string `str`. * * Appends an mruby string (`str2`) to another mruby string (`str`). * The mruby string `str` is modified in place. Handles self-appendage. */ MRB_API mrb_value mrb_str_cat_str(mrb_state *mrb, mrb_value str, mrb_value str2) { struct RString *s = mrb_str_ptr(str); struct RString *s2 = mrb_str_ptr(str2); if (s == s2) { mrb_str_modify(mrb, s); } /* Appended bytes that were read as bytes and go above ASCII spell no character in the string they land in, so they hand it the byte reading along with themselves. ASCII bytes read the same under any reading and say nothing. Decided before the append so a raise leaves the string as it was, applied after so it lands on the string the append made. The flag only ever goes on: a string already read as bytes stays read that way whatever lands in it, which is where CRuby lifts an all-ASCII byte-read receiver to the argument's encoding. Taking the reading back off would mean a buffer turning into text partway through being filled. mrb_str_plus() decides a fresh string instead and answers differently for the same pair; the comment there has the boundary. */ mrb_bool binary = !RSTR_BINARY_P(s) && RSTR_BINARY_P(s2) && !str_ascii_p(s2); mrb_value ret = mrb_str_cat(mrb, str, RSTRING_PTR(str2), RSTRING_LEN(str2)); if (binary) { RSTR_ENCODING_SET(mrb_str_ptr(ret), MRB_STR_ENCODING_BINARY); } return ret; } /* * @param mrb The mruby state. * @param str1 The mruby string to append to (modified in place). * @param str2 The mruby value to append (will be converted to a string if not already one). * @return The modified mruby string `str1`. * * Appends an mruby value (`str2`) to an mruby string (`str1`). * `str2` is first ensured to be a string (converted if necessary). * Then, `str1` is modified in place. This is similar to `mrb_str_concat` * but `mrb_str_concat` takes `self` and `other` as parameters. */ MRB_API mrb_value mrb_str_append(mrb_state *mrb, mrb_value str1, mrb_value str2) { mrb_ensure_string_type(mrb, str2); return mrb_str_cat_str(mrb, str1, str2); } /* * call-seq: * str.inspect -> string * * Returns a printable version of _str_, surrounded by quote marks, * with special characters escaped. * * str = "hello" * str[3] = "\b" * str.inspect #=> "\"hel\\bo\"" */ /* * @param mrb The mruby state. * @param str The mruby string to inspect. * @return A new mruby string that is the inspect-representation of `str`. * * Returns a human-readable, printable version of the string, typically * surrounded by quotes and with special characters escaped. * UTF-8 characters are preserved if `MRB_UTF8_STRING` is defined and `inspect` is true. */ mrb_value mrb_str_inspect(mrb_state *mrb, mrb_value str) { return str_escape(mrb, str, TRUE); } /* * call-seq: * str.bytes -> array of int * * Returns an array of bytes in _str_. * * str = "hello" * str.bytes #=> [104, 101, 108, 108, 111] */ static mrb_value mrb_str_bytes(mrb_state *mrb, mrb_value str) { struct RString *s = mrb_str_ptr(str); mrb_value a = mrb_ary_new_capa(mrb, RSTR_LEN(s)); unsigned char *p = (unsigned char*)(RSTR_PTR(s)), *pend = p + RSTR_LEN(s); while (p < pend) { mrb_ary_push(mrb, a, mrb_fixnum_value(p[0])); p++; } return a; } /* * call-seq: * str.getbyte(index) -> 0 .. 255 * * returns the *index*th byte as an integer. */ static mrb_value mrb_str_getbyte(mrb_state *mrb, mrb_value str) { mrb_int pos; mrb_get_args(mrb, "i", &pos); if (pos < 0) pos += RSTRING_LEN(str); if (pos < 0 || RSTRING_LEN(str) <= pos) return mrb_nil_value(); return mrb_fixnum_value((unsigned char)RSTRING_PTR(str)[pos]); } /* * call-seq: * str.setbyte(index, integer) -> integer * * modifies the *index*th byte as *integer*. */ static mrb_value mrb_str_setbyte(mrb_state *mrb, mrb_value str) { mrb_int pos, byte; mrb_get_args(mrb, "ii", &pos, &byte); mrb_int len = RSTRING_LEN(str); if (pos < -len || len <= pos) mrb_raisef(mrb, E_INDEX_ERROR, "index %i out of string", pos); if (pos < 0) pos += len; mrb_str_modify(mrb, mrb_str_ptr(str)); byte &= 0xff; RSTRING_PTR(str)[pos] = (unsigned char)byte; return mrb_fixnum_value((unsigned char)byte); } /* * call-seq: * str.byteslice(integer) -> new_str or nil * str.byteslice(integer, integer) -> new_str or nil * str.byteslice(range) -> new_str or nil * * Byte Reference---If passed a single Integer, returns a * substring of one byte 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 * bytes 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. * The encoding of the resulted string keeps original encoding. * * "hello".byteslice(1) #=> "e" * "hello".byteslice(-1) #=> "o" * "hello".byteslice(1, 2) #=> "el" * "\x80\u3042".byteslice(1, 3) #=> "\u3042" * "\x03\u3042\xff".byteslice(1..3) #=> "\u3042" */ static mrb_value mrb_str_byteslice(mrb_state *mrb, mrb_value str) { mrb_value a1; mrb_int str_len, beg, len; mrb_bool empty = TRUE; len = mrb_get_argc(mrb); switch (len) { case 2: mrb_get_args(mrb, "ii", &beg, &len); str_len = RSTRING_LEN(str); break; case 1: a1 = mrb_get_arg1(mrb); str_len = RSTRING_LEN(str); if (mrb_range_p(a1)) { if (mrb_range_beg_len(mrb, a1, &beg, &len, str_len, TRUE) != MRB_RANGE_OK) { return mrb_nil_value(); } } else { beg = mrb_as_int(mrb, a1); len = 1; empty = FALSE; } break; default: mrb_argnum_error(mrb, len, 1, 2); break; } if (mrb_str_beg_len(str_len, &beg, &len) && (empty || len != 0)) { return mrb_str_byte_subseq(mrb, str, beg, len); } else { return mrb_nil_value(); } } static mrb_value sub_replace(mrb_state *mrb, mrb_value self) { mrb_value replace, pat; mrb_int found, offset; mrb_bool self_taken = FALSE, match_taken = FALSE; mrb_get_args(mrb, "SSi", &replace, &pat, &found); if (found < 0 || RSTRING_LEN(self) < found) { mrb_raise(mrb, E_RUNTIME_ERROR, "argument out of range"); } const char *p = RSTRING_PTR(replace); mrb_int plen = RSTRING_LEN(replace); const char *match = RSTRING_PTR(pat); mrb_int mlen = RSTRING_LEN(pat); mrb_value result = mrb_str_new(mrb, 0, 0); for (mrb_int i=0; i 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; } 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 << MRB_STR_EMBED_LEN_BITS), "pointer size too big for embedded string"); mrb->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()); }