diff options
Diffstat (limited to 'string.c')
| -rw-r--r-- | string.c | 3715 |
1 files changed, 2317 insertions, 1398 deletions
@@ -28,9 +28,11 @@ #include "internal/array.h" #include "internal/compar.h" #include "internal/compilers.h" +#include "internal/concurrent_set.h" #include "internal/encoding.h" #include "internal/error.h" #include "internal/gc.h" +#include "internal/hash.h" #include "internal/numeric.h" #include "internal/object.h" #include "internal/proc.h" @@ -41,9 +43,13 @@ #include "probes.h" #include "ruby/encoding.h" #include "ruby/re.h" +#include "ruby/thread.h" #include "ruby/util.h" +#include "ruby/ractor.h" #include "ruby_assert.h" +#include "shape.h" #include "vm_sync.h" +#include "ruby/internal/attr/nonstring.h" #if defined HAVE_CRYPT_R # if defined HAVE_CRYPT_H @@ -78,27 +84,52 @@ VALUE rb_cString; VALUE rb_cSymbol; -/* FLAGS of RString +/* Flags of RString * + * 0: STR_SHARED (equal to ELTS_SHARED) + * The string is shared. The buffer this string points to is owned by + * another string (the shared root). * 1: RSTRING_NOEMBED - * 2: STR_SHARED (== ELTS_SHARED) - * 5: STR_SHARED_ROOT (RSTRING_NOEMBED==1 && STR_SHARED == 0, there may be - * other strings that rely on this string's buffer) - * 6: STR_BORROWED (when RSTRING_NOEMBED==1 && klass==0, unsafe to recycle - * early, specific to rb_str_tmp_frozen_{acquire,release}) - * 7: STR_TMPLOCK (set when a pointer to the buffer is passed to syscall - * such as read(2). Any modification and realloc is prohibited) - * - * 8-9: ENC_CODERANGE (2 bits) - * 10-16: ENCODING (7 bits == 128) + * The string is not embedded. When a string is embedded, the contents + * follow the header. When a string is not embedded, the contents is + * on a separately allocated buffer. + * 2: STR_CHILLED_LITERAL (will be frozen in a future version) + * The string was allocated as a literal in a file without an explicit `frozen_string_literal` comment. + * It emits a deprecation warning when mutated for the first time. + * 3: STR_CHILLED_SYMBOL_TO_S (will be frozen in a future version) + * The string was allocated by the `Symbol#to_s` method. + * It emits a deprecation warning when mutated for the first time. + * 4: STR_PRECOMPUTED_HASH + * The string is embedded and has its precomputed hashcode stored + * after the terminator. + * 5: STR_SHARED_ROOT + * Other strings may point to the contents of this string. When this + * flag is set, STR_SHARED must not be set. + * 6: STR_BORROWED + * When RSTRING_NOEMBED is set and klass is 0, this string is unsafe + * to be unshared by rb_str_tmp_frozen_release. + * 7: STR_TMPLOCK + * The pointer to the buffer is passed to a system call such as + * read(2). Any modification and realloc is prohibited. + * 8-9: ENC_CODERANGE + * Stores the coderange of the string. + * 10-16: ENCODING + * Stores the encoding of the string. * 17: RSTRING_FSTR - * 18: STR_NOFREE (do not free this string's buffer when a String is freed. - * used for a string object based on C string literal) - * 19: STR_FAKESTR (when RVALUE is not managed by GC. Typically, the string - * object header is temporarily allocated on C stack) + * The string is a fstring. The string is deduplicated in the fstring + * table. + * 18: STR_NOFREE + * Do not free this string's buffer when the string is reclaimed + * by the garbage collector. Used for when the string buffer is a C + * string literal. + * 19: STR_FAKESTR + * The string is not allocated or managed by the garbage collector. + * Typically, the string object header (struct RString) is temporarily + * allocated on C stack. */ #define RUBY_MAX_CHAR_LEN 16 +#define STR_PRECOMPUTED_HASH FL_USER4 #define STR_SHARED_ROOT FL_USER5 #define STR_BORROWED FL_USER6 #define STR_TMPLOCK FL_USER7 @@ -109,17 +140,17 @@ VALUE rb_cSymbol; FL_SET((str), STR_NOEMBED);\ FL_UNSET((str), STR_SHARED | STR_SHARED_ROOT | STR_BORROWED);\ } while (0) -#define STR_SET_EMBED(str) FL_UNSET((str), (STR_NOEMBED|STR_NOFREE)) +#define STR_SET_EMBED(str) FL_UNSET((str), STR_NOEMBED | STR_SHARED | STR_NOFREE) #define STR_SET_LEN(str, n) do { \ RSTRING(str)->len = (n); \ } while (0) static inline bool -str_enc_fastpath(VALUE str) +str_encindex_fastpath(int encindex) { // The overwhelming majority of strings are in one of these 3 encodings. - switch (ENCODING_GET_INLINED(str)) { + switch (encindex) { case ENCINDEX_ASCII_8BIT: case ENCINDEX_UTF_8: case ENCINDEX_US_ASCII: @@ -129,6 +160,12 @@ str_enc_fastpath(VALUE str) } } +static inline bool +str_enc_fastpath(VALUE str) +{ + return str_encindex_fastpath(ENCODING_GET_INLINED(str)); +} + #define TERM_LEN(str) (str_enc_fastpath(str) ? 1 : rb_enc_mbminlen(rb_enc_from_index(ENCODING_GET(str)))) #define TERM_FILL(ptr, termlen) do {\ char *const term_fill_ptr = (ptr);\ @@ -155,7 +192,7 @@ str_enc_fastpath(VALUE str) }\ }\ else {\ - assert(!FL_TEST((str), STR_SHARED)); \ + RUBY_ASSERT(!FL_TEST((str), STR_SHARED)); \ SIZED_REALLOC_N(RSTRING(str)->as.heap.ptr, char, \ (size_t)(capacity) + (termlen), STR_HEAP_SIZE(str)); \ RSTRING(str)->as.heap.aux.capa = (capacity);\ @@ -164,10 +201,11 @@ str_enc_fastpath(VALUE str) #define STR_SET_SHARED(str, shared_str) do { \ if (!FL_TEST(str, STR_FAKESTR)) { \ - assert(RSTRING_PTR(shared_str) <= RSTRING_PTR(str)); \ - assert(RSTRING_PTR(str) <= RSTRING_PTR(shared_str) + RSTRING_LEN(shared_str)); \ + RUBY_ASSERT(RSTRING_PTR(shared_str) <= RSTRING_PTR(str)); \ + RUBY_ASSERT(RSTRING_PTR(str) <= RSTRING_PTR(shared_str) + RSTRING_LEN(shared_str)); \ RB_OBJ_WRITE((str), &RSTRING(str)->as.heap.aux.shared, (shared_str)); \ FL_SET((str), STR_SHARED); \ + rb_gc_register_pinning_obj(str); \ FL_SET((shared_str), STR_SHARED_ROOT); \ if (RBASIC_CLASS((shared_str)) == 0) /* for CoW-friendliness */ \ FL_SET_RAW((shared_str), STR_BORROWED); \ @@ -203,9 +241,11 @@ rb_str_reembeddable_p(VALUE str) } static inline size_t -rb_str_embed_size(long capa) +rb_str_embed_size(long capa, long termlen) { - return offsetof(struct RString, as.embed.ary) + capa; + size_t size = offsetof(struct RString, as.embed.ary) + capa + termlen; + if (size < sizeof(struct RString)) size = sizeof(struct RString); + return size; } size_t @@ -213,23 +253,30 @@ rb_str_size_as_embedded(VALUE str) { size_t real_size; if (STR_EMBED_P(str)) { - real_size = rb_str_embed_size(RSTRING(str)->len) + TERM_LEN(str); + size_t capa = RSTRING(str)->len; + if (FL_TEST_RAW(str, STR_PRECOMPUTED_HASH)) capa += sizeof(st_index_t); + + real_size = rb_str_embed_size(capa, TERM_LEN(str)); } /* if the string is not currently embedded, but it can be embedded, how * much space would it require */ else if (rb_str_reembeddable_p(str)) { - real_size = rb_str_embed_size(RSTRING(str)->as.heap.aux.capa) + TERM_LEN(str); + size_t capa = RSTRING(str)->as.heap.aux.capa; + if (FL_TEST_RAW(str, STR_PRECOMPUTED_HASH)) capa += sizeof(st_index_t); + + real_size = rb_str_embed_size(capa, TERM_LEN(str)); } else { real_size = sizeof(struct RString); } + return real_size; } static inline bool STR_EMBEDDABLE_P(long len, long termlen) { - return rb_gc_size_allocatable_p(rb_str_embed_size(len + termlen)); + return rb_gc_size_allocatable_p(rb_str_embed_size(len, termlen)); } static VALUE str_replace_shared_without_enc(VALUE str2, VALUE str); @@ -240,6 +287,7 @@ static VALUE str_new(VALUE klass, const char *ptr, long len); static void str_make_independent_expand(VALUE str, long len, long expand, const int termlen); static inline void str_modifiable(VALUE str); static VALUE rb_str_downcase(int argc, VALUE *argv, VALUE str); +static inline VALUE str_alloc_embed(VALUE klass, size_t capa); static inline void str_make_independent(VALUE str) @@ -280,26 +328,6 @@ rb_str_make_embedded(VALUE str) } void -rb_str_update_shared_ary(VALUE str, VALUE old_root, VALUE new_root) -{ - // if the root location hasn't changed, we don't need to update - if (new_root == old_root) { - return; - } - - // if the root string isn't embedded, we don't need to touch the pointer. - // it already points to the shame shared buffer - if (!STR_EMBED_P(new_root)) { - return; - } - - size_t offset = (size_t)((uintptr_t)RSTRING(str)->as.heap.ptr - (uintptr_t)RSTRING(old_root)->as.embed.ary); - - RUBY_ASSERT(RSTRING(str)->as.heap.ptr >= RSTRING(old_root)->as.embed.ary); - RSTRING(str)->as.heap.ptr = RSTRING(new_root)->as.embed.ary + offset; -} - -void rb_debug_rstring_null_ptr(const char *func) { fprintf(stderr, "%s is returning NULL!! " @@ -335,75 +363,49 @@ mustnot_wchar(VALUE str) } } -static int fstring_cmp(VALUE a, VALUE b); - -static VALUE register_fstring(VALUE str, bool copy); +static VALUE register_fstring(VALUE str, bool copy, bool force_precompute_hash); -const struct st_hash_type rb_fstring_hash_type = { - fstring_cmp, - rb_str_hash, -}; - -#define BARE_STRING_P(str) (!FL_ANY_RAW(str, FL_EXIVAR) && RBASIC_CLASS(str) == rb_cString) +#if SIZEOF_LONG == SIZEOF_VOIDP +#define PRECOMPUTED_FAKESTR_HASH 1 +#else +#endif -struct fstr_update_arg { - VALUE fstr; - bool copy; -}; +static inline bool +BARE_STRING_P(VALUE str) +{ + return RBASIC_CLASS(str) == rb_cString && !rb_shape_obj_has_ivars(str); +} -static int -fstr_update_callback(st_data_t *key, st_data_t *value, st_data_t data, int existing) +static inline st_index_t +str_do_hash(VALUE str) { + st_index_t h = rb_memhash((const void *)RSTRING_PTR(str), RSTRING_LEN(str)); + int e = RSTRING_LEN(str) ? ENCODING_GET(str) : 0; + if (e && !is_ascii_string(str)) { + h = rb_hash_end(rb_hash_uint32(h, (uint32_t)e)); + } + return h; +} - struct fstr_update_arg *arg = (struct fstr_update_arg *)data; - VALUE str = (VALUE)*key; +static VALUE +str_store_precomputed_hash(VALUE str, st_index_t hash) +{ + RUBY_ASSERT(!FL_TEST_RAW(str, STR_PRECOMPUTED_HASH)); + RUBY_ASSERT(STR_EMBED_P(str)); - if (existing) { - /* because of lazy sweep, str may be unmarked already and swept - * at next time */ +#if RUBY_DEBUG + size_t used_bytes = (RSTRING_LEN(str) + TERM_LEN(str)); + size_t free_bytes = str_embed_capa(str) - used_bytes; + RUBY_ASSERT(free_bytes >= sizeof(st_index_t)); +#endif - if (rb_objspace_garbage_object_p(str)) { - arg->fstr = Qundef; - return ST_DELETE; - } + memcpy(RSTRING_END(str) + TERM_LEN(str), &hash, sizeof(hash)); - arg->fstr = str; - return ST_STOP; - } - else { - if (FL_TEST_RAW(str, STR_FAKESTR)) { - if (arg->copy) { - VALUE new_str = str_new(rb_cString, RSTRING(str)->as.heap.ptr, RSTRING(str)->len); - rb_enc_copy(new_str, str); - str = new_str; - } - else { - str = str_new_static(rb_cString, RSTRING(str)->as.heap.ptr, - RSTRING(str)->len, - ENCODING_GET(str)); - } - OBJ_FREEZE_RAW(str); - } - else { - if (!OBJ_FROZEN(str)) - str = str_new_frozen(rb_cString, str); - if (STR_SHARED_P(str)) { /* str should not be shared */ - /* shared substring */ - str_make_independent(str); - assert(OBJ_FROZEN(str)); - } - if (!BARE_STRING_P(str)) { - str = str_new_frozen(rb_cString, str); - } - } - RBASIC(str)->flags |= RSTRING_FSTR; + FL_SET(str, STR_PRECOMPUTED_HASH); - *key = *value = arg->fstr = str; - return ST_CONTINUE; - } + return str; } -RUBY_FUNC_EXPORTED VALUE rb_fstring(VALUE str) { @@ -418,56 +420,223 @@ rb_fstring(VALUE str) bare = BARE_STRING_P(str); if (!bare) { if (STR_EMBED_P(str)) { - OBJ_FREEZE_RAW(str); + OBJ_FREEZE(str); return str; } - if (FL_TEST_RAW(str, STR_NOEMBED|STR_SHARED_ROOT|STR_SHARED) == (STR_NOEMBED|STR_SHARED_ROOT)) { - assert(OBJ_FROZEN(str)); + + if (FL_TEST_RAW(str, STR_SHARED_ROOT | STR_SHARED) == STR_SHARED_ROOT) { + RUBY_ASSERT(OBJ_FROZEN(str)); return str; } } - if (!OBJ_FROZEN(str)) + if (!FL_TEST_RAW(str, FL_FREEZE | STR_NOFREE | STR_CHILLED)) rb_str_resize(str, RSTRING_LEN(str)); - fstr = register_fstring(str, FALSE); + fstr = register_fstring(str, false, false); if (!bare) { str_replace_shared_without_enc(str, fstr); - OBJ_FREEZE_RAW(str); + OBJ_FREEZE(str); return str; } return fstr; } +static VALUE fstring_table_obj; + static VALUE -register_fstring(VALUE str, bool copy) +fstring_concurrent_set_hash(VALUE str) +{ +#ifdef PRECOMPUTED_FAKESTR_HASH + st_index_t h; + if (FL_TEST_RAW(str, STR_FAKESTR)) { + // register_fstring precomputes the hash and stores it in capa for fake strings + h = (st_index_t)RSTRING(str)->as.heap.aux.capa; + } + else { + h = rb_str_hash(str); + } + // rb_str_hash doesn't include the encoding for ascii only strings, so + // we add it to avoid common collisions between `:sym.name` (ASCII) and `"sym"` (UTF-8) + return (VALUE)rb_hash_end(rb_hash_uint32(h, (uint32_t)ENCODING_GET_INLINED(str))); +#else + return (VALUE)rb_str_hash(str); +#endif +} + +static bool +fstring_concurrent_set_cmp(VALUE a, VALUE b) { - struct fstr_update_arg args; - args.copy = copy; + long alen, blen; + const char *aptr, *bptr; - RB_VM_LOCK_ENTER(); - { - st_table *frozen_strings = rb_vm_fstring_table(); - do { - args.fstr = str; - st_update(frozen_strings, (st_data_t)str, fstr_update_callback, (st_data_t)&args); - } while (UNDEF_P(args.fstr)); + RUBY_ASSERT(RB_TYPE_P(a, T_STRING)); + RUBY_ASSERT(RB_TYPE_P(b, T_STRING)); + + RSTRING_GETMEM(a, aptr, alen); + RSTRING_GETMEM(b, bptr, blen); + return (alen == blen && + ENCODING_GET(a) == ENCODING_GET(b) && + memcmp(aptr, bptr, alen) == 0); +} + +struct fstr_create_arg { + bool copy; + bool force_precompute_hash; +}; + +static VALUE +fstring_concurrent_set_create(VALUE str, void *data) +{ + struct fstr_create_arg *arg = data; + + // Unless the string is empty or binary, its coderange has been precomputed. + int coderange = ENC_CODERANGE(str); + + if (FL_TEST_RAW(str, STR_FAKESTR)) { + if (arg->copy) { + VALUE new_str; + long len = RSTRING_LEN(str); + long capa = len + sizeof(st_index_t); + int term_len = TERM_LEN(str); + + if (arg->force_precompute_hash && STR_EMBEDDABLE_P(capa, term_len)) { + new_str = str_alloc_embed(rb_cString, capa + term_len); + memcpy(RSTRING_PTR(new_str), RSTRING_PTR(str), len); + STR_SET_LEN(new_str, RSTRING_LEN(str)); + TERM_FILL(RSTRING_END(new_str), TERM_LEN(str)); + rb_enc_copy(new_str, str); + str_store_precomputed_hash(new_str, str_do_hash(str)); + } + else { + new_str = str_new(rb_cString, RSTRING(str)->as.heap.ptr, RSTRING(str)->len); + rb_enc_copy(new_str, str); +#ifdef PRECOMPUTED_FAKESTR_HASH + if (rb_str_capacity(new_str) >= RSTRING_LEN(str) + term_len + sizeof(st_index_t)) { + str_store_precomputed_hash(new_str, (st_index_t)RSTRING(str)->as.heap.aux.capa); + } +#endif + } + str = new_str; + } + else { + str = str_new_static(rb_cString, RSTRING(str)->as.heap.ptr, + RSTRING(str)->len, + ENCODING_GET(str)); + } + OBJ_FREEZE(str); + } + else { + if (!OBJ_FROZEN(str) || CHILLED_STRING_P(str)) { + str = str_new_frozen(rb_cString, str); + } + if (STR_SHARED_P(str)) { /* str should not be shared */ + /* shared substring */ + str_make_independent(str); + RUBY_ASSERT(OBJ_FROZEN(str)); + } + if (!BARE_STRING_P(str)) { + str = str_new_frozen(rb_cString, str); + } + } + + ENC_CODERANGE_SET(str, coderange); + RBASIC(str)->flags |= RSTRING_FSTR; + if (!RB_OBJ_SHAREABLE_P(str)) { + RB_OBJ_SET_SHAREABLE(str); } - RB_VM_LOCK_LEAVE(); + RUBY_ASSERT((rb_gc_verify_shareable(str), 1)); + RUBY_ASSERT(RB_TYPE_P(str, T_STRING)); + RUBY_ASSERT(OBJ_FROZEN(str)); + RUBY_ASSERT(!FL_TEST_RAW(str, STR_FAKESTR)); + RUBY_ASSERT(!rb_shape_obj_has_ivars(str)); + RUBY_ASSERT(RBASIC_CLASS(str) == rb_cString); + RUBY_ASSERT(!rb_objspace_garbage_object_p(str)); - assert(OBJ_FROZEN(args.fstr)); - assert(!FL_TEST_RAW(args.fstr, STR_FAKESTR)); - assert(!FL_TEST_RAW(args.fstr, FL_EXIVAR)); - assert(RBASIC_CLASS(args.fstr) == rb_cString); - return args.fstr; + return str; +} + +static const struct rb_concurrent_set_funcs fstring_concurrent_set_funcs = { + .hash = fstring_concurrent_set_hash, + .cmp = fstring_concurrent_set_cmp, + .create = fstring_concurrent_set_create, + .free = NULL, +}; + +void +Init_fstring_table(void) +{ + fstring_table_obj = rb_concurrent_set_new(&fstring_concurrent_set_funcs, 8192); + rb_gc_register_address(&fstring_table_obj); +} + +static VALUE +register_fstring(VALUE str, bool copy, bool force_precompute_hash) +{ + struct fstr_create_arg args = { + .copy = copy, + .force_precompute_hash = force_precompute_hash + }; + +#if SIZEOF_VOIDP == SIZEOF_LONG + if (FL_TEST_RAW(str, STR_FAKESTR)) { + // if the string hasn't been interned, we'll need the hash twice, so we + // compute it once and store it in capa + RSTRING(str)->as.heap.aux.capa = (long)str_do_hash(str); + } +#endif + + VALUE result = rb_concurrent_set_find_or_insert(&fstring_table_obj, str, &args); + + RUBY_ASSERT(!rb_objspace_garbage_object_p(result)); + RUBY_ASSERT(RB_TYPE_P(result, T_STRING)); + RUBY_ASSERT(OBJ_FROZEN(result)); + RUBY_ASSERT(RB_OBJ_SHAREABLE_P(result)); + RUBY_ASSERT((rb_gc_verify_shareable(result), 1)); + RUBY_ASSERT(!FL_TEST_RAW(result, STR_FAKESTR)); + RUBY_ASSERT(RBASIC_CLASS(result) == rb_cString); + + return result; +} + +bool +rb_obj_is_fstring_table(VALUE obj) +{ + ASSERT_vm_locking(); + + return obj == fstring_table_obj; +} + +void +rb_gc_free_fstring(VALUE obj) +{ + ASSERT_vm_locking_with_barrier(); + + RUBY_ASSERT(FL_TEST(obj, RSTRING_FSTR)); + RUBY_ASSERT(OBJ_FROZEN(obj)); + RUBY_ASSERT(!FL_TEST(obj, STR_SHARED)); + + rb_concurrent_set_delete_by_identity(fstring_table_obj, obj); + + RB_DEBUG_COUNTER_INC(obj_str_fstr); + + FL_UNSET(obj, RSTRING_FSTR); +} + +void +rb_fstring_foreach_with_replace(int (*callback)(VALUE *str, void *data), void *data) +{ + if (fstring_table_obj) { + rb_concurrent_set_foreach_with_replace(fstring_table_obj, callback, data); + } } static VALUE setup_fake_str(struct RString *fake_str, const char *name, long len, int encidx) { fake_str->basic.flags = T_STRING|RSTRING_NOEMBED|STR_NOFREE|STR_FAKESTR; - /* SHARED to be allocated by the callback */ + RBASIC_SET_SHAPE_ID((VALUE)fake_str, ROOT_SHAPE_ID); if (!name) { RUBY_ASSERT_ALWAYS(len == 0); @@ -500,15 +669,15 @@ rb_setup_fake_str(struct RString *fake_str, const char *name, long len, rb_encod VALUE rb_fstring_new(const char *ptr, long len) { - struct RString fake_str; - return register_fstring(setup_fake_str(&fake_str, ptr, len, ENCINDEX_US_ASCII), FALSE); + struct RString fake_str = {RBASIC_INIT}; + return register_fstring(setup_fake_str(&fake_str, ptr, len, ENCINDEX_US_ASCII), false, false); } VALUE rb_fstring_enc_new(const char *ptr, long len, rb_encoding *enc) { - struct RString fake_str; - return register_fstring(rb_setup_fake_str(&fake_str, ptr, len, enc), FALSE); + struct RString fake_str = {RBASIC_INIT}; + return register_fstring(rb_setup_fake_str(&fake_str, ptr, len, enc), false, false); } VALUE @@ -517,41 +686,30 @@ rb_fstring_cstr(const char *ptr) return rb_fstring_new(ptr, strlen(ptr)); } -static int -fstring_set_class_i(st_data_t key, st_data_t val, st_data_t arg) -{ - RBASIC_SET_CLASS((VALUE)key, (VALUE)arg); - return ST_CONTINUE; -} - -static int -fstring_cmp(VALUE a, VALUE b) -{ - long alen, blen; - const char *aptr, *bptr; - RSTRING_GETMEM(a, aptr, alen); - RSTRING_GETMEM(b, bptr, blen); - return (alen != blen || - ENCODING_GET(a) != ENCODING_GET(b) || - memcmp(aptr, bptr, alen) != 0); -} - -static inline int +static inline bool single_byte_optimizable(VALUE str) { - rb_encoding *enc; - + int encindex = ENCODING_GET(str); + switch (encindex) { + case ENCINDEX_ASCII_8BIT: + case ENCINDEX_US_ASCII: + return true; + case ENCINDEX_UTF_8: + // For UTF-8 it's worth scanning the string coderange when unknown. + return rb_enc_str_coderange(str) == ENC_CODERANGE_7BIT; + } /* Conservative. It may be ENC_CODERANGE_UNKNOWN. */ - if (ENC_CODERANGE(str) == ENC_CODERANGE_7BIT) - return 1; + if (ENC_CODERANGE(str) == ENC_CODERANGE_7BIT) { + return true; + } - enc = STR_ENC_GET(str); - if (rb_enc_mbmaxlen(enc) == 1) - return 1; + if (rb_enc_mbmaxlen(rb_enc_from_index(encindex)) == 1) { + return true; + } /* Conservative. Possibly single byte. * "\xa1" in Shift_JIS for example. */ - return 0; + return false; } VALUE rb_fs; @@ -559,7 +717,7 @@ VALUE rb_fs; static inline const char * search_nonascii(const char *p, const char *e) { - const uintptr_t *s, *t; + const char *s, *t; #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) # if SIZEOF_UINTPTR_T == 8 @@ -603,17 +761,19 @@ search_nonascii(const char *p, const char *e) #define aligned_ptr(value) \ __builtin_assume_aligned((value), sizeof(uintptr_t)) #else -#define aligned_ptr(value) (uintptr_t *)(value) +#define aligned_ptr(value) (value) #endif s = aligned_ptr(p); - t = (uintptr_t *)(e - (SIZEOF_VOIDP-1)); + t = (e - (SIZEOF_VOIDP-1)); #undef aligned_ptr - for (;s < t; s++) { - if (*s & NONASCII_MASK) { + for (;s < t; s += sizeof(uintptr_t)) { + uintptr_t word; + memcpy(&word, s, sizeof(word)); + if (word & NONASCII_MASK) { #ifdef WORDS_BIGENDIAN - return (const char *)s + (nlz_intptr(*s&NONASCII_MASK)>>3); + return (const char *)s + (nlz_intptr(word&NONASCII_MASK)>>3); #else - return (const char *)s + (ntz_intptr(*s&NONASCII_MASK)>>3); + return (const char *)s + (ntz_intptr(word&NONASCII_MASK)>>3); #endif } } @@ -796,16 +956,24 @@ rb_enc_str_coderange(VALUE str) return cr; } +static inline bool +rb_enc_str_asciicompat(VALUE str) +{ + int encindex = ENCODING_GET_INLINED(str); + return str_encindex_fastpath(encindex) || rb_enc_asciicompat(rb_enc_get_from_index(encindex)); +} + int rb_enc_str_asciionly_p(VALUE str) { - rb_encoding *enc = STR_ENC_GET(str); - - if (!rb_enc_asciicompat(enc)) - return FALSE; - else if (is_ascii_string(str)) - return TRUE; - return FALSE; + switch(ENC_CODERANGE(str)) { + case ENC_CODERANGE_UNKNOWN: + return rb_enc_str_asciicompat(str) && is_ascii_string(str); + case ENC_CODERANGE_7BIT: + return true; + default: + return false; + } } static inline void @@ -822,7 +990,7 @@ str_capacity(VALUE str, const int termlen) if (STR_EMBED_P(str)) { return str_embed_capa(str) - termlen; } - else if (FL_TEST(str, STR_SHARED|STR_NOFREE)) { + else if (FL_ANY_RAW(str, STR_SHARED|STR_NOFREE)) { return RSTRING(str)->len; } else { @@ -847,13 +1015,16 @@ must_not_null(const char *ptr) static inline VALUE str_alloc_embed(VALUE klass, size_t capa) { - size_t size = rb_str_embed_size(capa); - assert(size > 0); - assert(rb_gc_size_allocatable_p(size)); + size_t size = rb_str_embed_size(capa, 0); + RUBY_ASSERT(size > 0); + RUBY_ASSERT(rb_gc_size_allocatable_p(size)); NEWOBJ_OF(str, struct RString, klass, T_STRING | (RGENGC_WB_PROTECTED_STRING ? FL_WB_PROTECTED : 0), size, 0); + str->len = 0; + str->as.embed.ary[0] = 0; + return (VALUE)str; } @@ -863,6 +1034,10 @@ str_alloc_heap(VALUE klass) NEWOBJ_OF(str, struct RString, klass, T_STRING | STR_NOEMBED | (RGENGC_WB_PROTECTED_STRING ? FL_WB_PROTECTED : 0), sizeof(struct RString), 0); + str->len = 0; + str->as.heap.aux.capa = 0; + str->as.heap.ptr = NULL; + return (VALUE)str; } @@ -872,11 +1047,12 @@ empty_str_alloc(VALUE klass) RUBY_DTRACE_CREATE_HOOK(STRING, 0); VALUE str = str_alloc_embed(klass, 0); memset(RSTRING(str)->as.embed.ary, 0, str_embed_capa(str)); + ENC_CODERANGE_SET(str, ENC_CODERANGE_7BIT); return str; } static VALUE -str_new0(VALUE klass, const char *ptr, long len, int termlen) +str_enc_new(VALUE klass, const char *ptr, long len, rb_encoding *enc) { VALUE str; @@ -884,12 +1060,18 @@ str_new0(VALUE klass, const char *ptr, long len, int termlen) rb_raise(rb_eArgError, "negative string size (or size too big)"); } + if (enc == NULL) { + enc = rb_ascii8bit_encoding(); + } + RUBY_DTRACE_CREATE_HOOK(STRING, len); + int termlen = rb_enc_mbminlen(enc); + if (STR_EMBEDDABLE_P(len, termlen)) { str = str_alloc_embed(klass, len + termlen); if (len == 0) { - ENC_CODERANGE_SET(str, ENC_CODERANGE_7BIT); + ENC_CODERANGE_SET(str, rb_enc_asciicompat(enc) ? ENC_CODERANGE_7BIT : ENC_CODERANGE_VALID); } } else { @@ -901,9 +1083,16 @@ str_new0(VALUE klass, const char *ptr, long len, int termlen) RSTRING(str)->as.heap.ptr = rb_xmalloc_mul_add_mul(sizeof(char), len, sizeof(char), termlen); } + + rb_enc_raw_set(str, enc); + if (ptr) { memcpy(RSTRING_PTR(str), ptr, len); } + else { + memset(RSTRING_PTR(str), 0, len); + } + STR_SET_LEN(str, len); TERM_FILL(RSTRING_PTR(str) + len, termlen); return str; @@ -912,7 +1101,7 @@ str_new0(VALUE klass, const char *ptr, long len, int termlen) static VALUE str_new(VALUE klass, const char *ptr, long len) { - return str_new0(klass, ptr, len, 1); + return str_enc_new(klass, ptr, len, rb_ascii8bit_encoding()); } VALUE @@ -924,29 +1113,19 @@ rb_str_new(const char *ptr, long len) VALUE rb_usascii_str_new(const char *ptr, long len) { - VALUE str = rb_str_new(ptr, len); - ENCODING_CODERANGE_SET(str, rb_usascii_encindex(), ENC_CODERANGE_7BIT); - return str; + return str_enc_new(rb_cString, ptr, len, rb_usascii_encoding()); } VALUE rb_utf8_str_new(const char *ptr, long len) { - VALUE str = str_new(rb_cString, ptr, len); - rb_enc_associate_index(str, rb_utf8_encindex()); - return str; + return str_enc_new(rb_cString, ptr, len, rb_utf8_encoding()); } VALUE rb_enc_str_new(const char *ptr, long len, rb_encoding *enc) { - VALUE str; - - if (!enc) return rb_str_new(ptr, len); - - str = str_new0(rb_cString, ptr, len, rb_enc_mbminlen(enc)); - rb_enc_associate(str, enc); - return str; + return str_enc_new(rb_cString, ptr, len, enc); } VALUE @@ -964,17 +1143,13 @@ rb_str_new_cstr(const char *ptr) VALUE rb_usascii_str_new_cstr(const char *ptr) { - VALUE str = rb_str_new_cstr(ptr); - ENCODING_CODERANGE_SET(str, rb_usascii_encindex(), ENC_CODERANGE_7BIT); - return str; + return rb_enc_str_new_cstr(ptr, rb_usascii_encoding()); } VALUE rb_utf8_str_new_cstr(const char *ptr) { - VALUE str = rb_str_new_cstr(ptr); - rb_enc_associate_index(str, rb_utf8_encindex()); - return str; + return rb_enc_str_new_cstr(ptr, rb_utf8_encoding()); } VALUE @@ -997,8 +1172,7 @@ str_new_static(VALUE klass, const char *ptr, long len, int encindex) } if (!ptr) { - rb_encoding *enc = rb_enc_get_from_index(encindex); - str = str_new0(klass, ptr, len, rb_enc_mbminlen(enc)); + str = str_enc_new(klass, ptr, len, rb_enc_from_index(encindex)); } else { RUBY_DTRACE_CREATE_HOOK(STRING, len); @@ -1007,8 +1181,8 @@ str_new_static(VALUE klass, const char *ptr, long len, int encindex) RSTRING(str)->as.heap.ptr = (char *)ptr; RSTRING(str)->as.heap.aux.capa = len; RBASIC(str)->flags |= STR_NOFREE; + rb_enc_associate_index(str, encindex); } - rb_enc_associate_index(str, encindex); return str; } @@ -1151,6 +1325,7 @@ str_cat_conv_enc_opts(VALUE newstr, long ofs, const char *ptr, long len, rb_str_resize(newstr, olen); } DATA_PTR(econv_wrapper) = 0; + RB_GC_GUARD(econv_wrapper); rb_econv_close(ec); switch (ret) { case econv_finished: @@ -1299,7 +1474,8 @@ str_replace_shared_without_enc(VALUE str2, VALUE str) root = rb_str_new_frozen(str); RSTRING_GETMEM(root, ptr, len); } - assert(OBJ_FROZEN(root)); + RUBY_ASSERT(OBJ_FROZEN(root)); + if (!STR_EMBED_P(str2) && !FL_TEST_RAW(str2, STR_SHARED|STR_NOFREE)) { if (FL_TEST_RAW(str2, STR_SHARED_ROOT)) { rb_fatal("about to free a possible shared root"); @@ -1342,7 +1518,7 @@ rb_str_new_shared(VALUE str) VALUE rb_str_new_frozen(VALUE orig) { - if (OBJ_FROZEN(orig)) return orig; + if (RB_FL_TEST_RAW(orig, FL_FREEZE | STR_CHILLED) == FL_FREEZE) return orig; return str_new_frozen(rb_obj_class(orig), orig); } @@ -1353,6 +1529,14 @@ rb_str_new_frozen_String(VALUE orig) return str_new_frozen(rb_cString, orig); } + +VALUE +rb_str_frozen_bare_string(VALUE orig) +{ + if (RB_LIKELY(BARE_STRING_P(orig) && OBJ_FROZEN_RAW(orig))) return orig; + return str_new_frozen(rb_cString, orig); +} + VALUE rb_str_tmp_frozen_acquire(VALUE orig) { @@ -1360,6 +1544,46 @@ rb_str_tmp_frozen_acquire(VALUE orig) return str_new_frozen_buffer(0, orig, FALSE); } +VALUE +rb_str_tmp_frozen_no_embed_acquire(VALUE orig) +{ + if (OBJ_FROZEN_RAW(orig) && !STR_EMBED_P(orig) && !rb_str_reembeddable_p(orig)) return orig; + if (STR_SHARED_P(orig) && !STR_EMBED_P(RSTRING(orig)->as.heap.aux.shared)) return rb_str_tmp_frozen_acquire(orig); + + VALUE str = str_alloc_heap(0); + OBJ_FREEZE(str); + /* Always set the STR_SHARED_ROOT to ensure it does not get re-embedded. */ + FL_SET(str, STR_SHARED_ROOT); + + size_t capa = str_capacity(orig, TERM_LEN(orig)); + + /* If the string is embedded then we want to create a copy that is heap + * allocated. If the string is shared then the shared root must be + * embedded, so we want to create a copy. If the string is a shared root + * then it must be embedded, so we want to create a copy. */ + if (STR_EMBED_P(orig) || FL_TEST_RAW(orig, STR_SHARED | STR_SHARED_ROOT | RSTRING_FSTR)) { + RSTRING(str)->as.heap.ptr = rb_xmalloc_mul_add_mul(sizeof(char), capa, sizeof(char), TERM_LEN(orig)); + memcpy(RSTRING(str)->as.heap.ptr, RSTRING_PTR(orig), capa); + } + else { + /* orig must be heap allocated and not shared, so we can safely transfer + * the pointer to str. */ + RSTRING(str)->as.heap.ptr = RSTRING(orig)->as.heap.ptr; + RBASIC(str)->flags |= RBASIC(orig)->flags & STR_NOFREE; + RBASIC(orig)->flags &= ~STR_NOFREE; + STR_SET_SHARED(orig, str); + if (RB_OBJ_SHAREABLE_P(orig)) { + RB_OBJ_SET_SHAREABLE(str); + RUBY_ASSERT((rb_gc_verify_shareable(str), 1)); + } + } + + RSTRING(str)->len = RSTRING(orig)->len; + RSTRING(str)->as.heap.aux.capa = capa; + + return str; +} + void rb_str_tmp_frozen_release(VALUE orig, VALUE tmp) { @@ -1367,21 +1591,21 @@ rb_str_tmp_frozen_release(VALUE orig, VALUE tmp) return; if (STR_EMBED_P(tmp)) { - assert(OBJ_FROZEN_RAW(tmp)); + RUBY_ASSERT(OBJ_FROZEN_RAW(tmp)); } - else if (FL_TEST_RAW(orig, STR_SHARED) && - !FL_TEST_RAW(orig, STR_TMPLOCK|RUBY_FL_FREEZE)) { + else if (FL_TEST_RAW(orig, STR_SHARED | STR_TMPLOCK) == STR_TMPLOCK && + !OBJ_FROZEN_RAW(orig)) { VALUE shared = RSTRING(orig)->as.heap.aux.shared; if (shared == tmp && !FL_TEST_RAW(tmp, STR_BORROWED)) { - assert(RSTRING(orig)->as.heap.ptr == RSTRING(tmp)->as.heap.ptr); - assert(RSTRING_LEN(orig) == RSTRING_LEN(tmp)); + RUBY_ASSERT(RSTRING(orig)->as.heap.ptr == RSTRING(tmp)->as.heap.ptr); + RUBY_ASSERT(RSTRING_LEN(orig) == RSTRING_LEN(tmp)); /* Unshare orig since the root (tmp) only has this one child. */ FL_UNSET_RAW(orig, STR_SHARED); RSTRING(orig)->as.heap.aux.capa = RSTRING(tmp)->as.heap.aux.capa; RBASIC(orig)->flags |= RBASIC(tmp)->flags & STR_NOFREE; - assert(OBJ_FROZEN_RAW(tmp)); + RUBY_ASSERT(OBJ_FROZEN_RAW(tmp)); /* Make tmp embedded and empty so it is safe for sweeping. */ STR_SET_EMBED(tmp); @@ -1399,8 +1623,9 @@ str_new_frozen(VALUE klass, VALUE orig) static VALUE heap_str_make_shared(VALUE klass, VALUE orig) { - assert(!STR_EMBED_P(orig)); - assert(!STR_SHARED_P(orig)); + RUBY_ASSERT(!STR_EMBED_P(orig)); + RUBY_ASSERT(!STR_SHARED_P(orig)); + RUBY_ASSERT(!RB_OBJ_SHAREABLE_P(orig)); VALUE str = str_alloc_heap(klass); STR_SET_LEN(str, RSTRING_LEN(orig)); @@ -1410,7 +1635,7 @@ heap_str_make_shared(VALUE klass, VALUE orig) RBASIC(orig)->flags &= ~STR_NOFREE; STR_SET_SHARED(orig, str); if (klass == 0) - FL_UNSET_RAW(str, STR_BORROWED); + FL_UNSET_RAW(str, STR_BORROWED); return str; } @@ -1420,27 +1645,28 @@ str_new_frozen_buffer(VALUE klass, VALUE orig, int copy_encoding) VALUE str; long len = RSTRING_LEN(orig); + rb_encoding *enc = copy_encoding ? STR_ENC_GET(orig) : rb_ascii8bit_encoding(); int termlen = copy_encoding ? TERM_LEN(orig) : 1; if (STR_EMBED_P(orig) || STR_EMBEDDABLE_P(len, termlen)) { - str = str_new0(klass, RSTRING_PTR(orig), len, termlen); - assert(STR_EMBED_P(str)); + str = str_enc_new(klass, RSTRING_PTR(orig), len, enc); + RUBY_ASSERT(STR_EMBED_P(str)); } else { if (FL_TEST_RAW(orig, STR_SHARED)) { VALUE shared = RSTRING(orig)->as.heap.aux.shared; long ofs = RSTRING(orig)->as.heap.ptr - RSTRING_PTR(shared); long rest = RSTRING_LEN(shared) - ofs - RSTRING_LEN(orig); - assert(ofs >= 0); - assert(rest >= 0); - assert(ofs + rest <= RSTRING_LEN(shared)); - assert(OBJ_FROZEN(shared)); + RUBY_ASSERT(ofs >= 0); + RUBY_ASSERT(rest >= 0); + RUBY_ASSERT(ofs + rest <= RSTRING_LEN(shared)); + RUBY_ASSERT(OBJ_FROZEN(shared)); if ((ofs > 0) || (rest > 0) || (klass != RBASIC(shared)->klass) || ENCODING_GET(shared) != ENCODING_GET(orig)) { str = str_new_shared(klass, shared); - assert(!STR_EMBED_P(str)); + RUBY_ASSERT(!STR_EMBED_P(str)); RSTRING(str)->as.heap.ptr += ofs; STR_SET_LEN(str, RSTRING_LEN(str) - (ofs + rest)); } @@ -1455,10 +1681,16 @@ str_new_frozen_buffer(VALUE klass, VALUE orig, int copy_encoding) STR_SET_EMBED(str); memcpy(RSTRING_PTR(str), RSTRING_PTR(orig), RSTRING_LEN(orig)); STR_SET_LEN(str, RSTRING_LEN(orig)); + ENC_CODERANGE_SET(str, ENC_CODERANGE(orig)); TERM_FILL(RSTRING_END(str), TERM_LEN(orig)); } else { - str = heap_str_make_shared(klass, orig); + if (RB_OBJ_SHAREABLE_P(orig)) { + str = str_new(klass, RSTRING_PTR(orig), RSTRING_LEN(orig)); + } + else { + str = heap_str_make_shared(klass, orig); + } } } @@ -1470,7 +1702,7 @@ str_new_frozen_buffer(VALUE klass, VALUE orig, int copy_encoding) VALUE rb_str_new_with_class(VALUE obj, const char *ptr, long len) { - return str_new0(rb_obj_class(obj), ptr, len, TERM_LEN(obj)); + return str_enc_new(rb_obj_class(obj), ptr, len, STR_ENC_GET(obj)); } static VALUE @@ -1520,17 +1752,6 @@ rb_str_tmp_new(long len) void rb_str_free(VALUE str) { - if (FL_TEST(str, RSTRING_FSTR)) { - st_data_t fstr = (st_data_t)str; - - RB_VM_LOCK_ENTER(); - { - st_delete(rb_vm_fstring_table(), &fstr, NULL); - RB_DEBUG_COUNTER_INC(obj_str_fstr); - } - RB_VM_LOCK_LEAVE(); - } - if (STR_EMBED_P(str)) { RB_DEBUG_COUNTER_INC(obj_str_embed); } @@ -1544,7 +1765,7 @@ rb_str_free(VALUE str) } } -RUBY_FUNC_EXPORTED size_t +size_t rb_str_memsize(VALUE str) { if (FL_TEST(str, STR_NOEMBED|STR_SHARED|STR_NOFREE) == STR_NOEMBED) { @@ -1593,9 +1814,9 @@ str_shared_replace(VALUE str, VALUE str2) } else { if (STR_EMBED_P(str2)) { - assert(!FL_TEST(str2, STR_SHARED)); + RUBY_ASSERT(!FL_TEST(str2, STR_SHARED)); long len = RSTRING_LEN(str2); - assert(len + termlen <= str_embed_capa(str2)); + RUBY_ASSERT(len + termlen <= str_embed_capa(str2)); char *new_ptr = ALLOC_N(char, len + termlen); memcpy(new_ptr, RSTRING(str2)->as.embed.ary, len + termlen); @@ -1654,7 +1875,7 @@ str_replace(VALUE str, VALUE str2) len = RSTRING_LEN(str2); if (STR_SHARED_P(str2)) { VALUE shared = RSTRING(str2)->as.heap.aux.shared; - assert(OBJ_FROZEN(shared)); + RUBY_ASSERT(OBJ_FROZEN(shared)); STR_SET_NOEMBED(str); STR_SET_LEN(str, len); RSTRING(str)->as.heap.ptr = RSTRING_PTR(str2); @@ -1671,13 +1892,15 @@ str_replace(VALUE str, VALUE str2) static inline VALUE ec_str_alloc_embed(struct rb_execution_context_struct *ec, VALUE klass, size_t capa) { - size_t size = rb_str_embed_size(capa); - assert(size > 0); - assert(rb_gc_size_allocatable_p(size)); + size_t size = rb_str_embed_size(capa, 0); + RUBY_ASSERT(size > 0); + RUBY_ASSERT(rb_gc_size_allocatable_p(size)); NEWOBJ_OF(str, struct RString, klass, T_STRING | (RGENGC_WB_PROTECTED_STRING ? FL_WB_PROTECTED : 0), size, ec); + str->len = 0; + return (VALUE)str; } @@ -1687,45 +1910,16 @@ ec_str_alloc_heap(struct rb_execution_context_struct *ec, VALUE klass) NEWOBJ_OF(str, struct RString, klass, T_STRING | STR_NOEMBED | (RGENGC_WB_PROTECTED_STRING ? FL_WB_PROTECTED : 0), sizeof(struct RString), ec); + str->as.heap.aux.capa = 0; + str->as.heap.ptr = NULL; + return (VALUE)str; } static inline VALUE -str_duplicate_setup(VALUE klass, VALUE str, VALUE dup) +str_duplicate_setup_encoding(VALUE str, VALUE dup, VALUE flags) { - const VALUE flag_mask = - ENC_CODERANGE_MASK | ENCODING_MASK | - FL_FREEZE - ; - VALUE flags = FL_TEST_RAW(str, flag_mask); int encidx = 0; - if (STR_EMBED_P(str)) { - long len = RSTRING_LEN(str); - - assert(STR_EMBED_P(dup)); - assert(str_embed_capa(dup) >= len + 1); - MEMCPY(RSTRING(dup)->as.embed.ary, RSTRING(str)->as.embed.ary, char, len + 1); - } - else { - VALUE root = str; - if (FL_TEST_RAW(str, STR_SHARED)) { - root = RSTRING(str)->as.heap.aux.shared; - } - else if (UNLIKELY(!(flags & FL_FREEZE))) { - root = str = str_new_frozen(klass, str); - flags = FL_TEST_RAW(str, flag_mask); - } - assert(!STR_SHARED_P(root)); - assert(RB_OBJ_FROZEN_RAW(root)); - - RSTRING(dup)->as.heap.ptr = RSTRING_PTR(str); - FL_SET(root, STR_SHARED_ROOT); - RB_OBJ_WRITE(dup, &RSTRING(dup)->as.heap.aux.shared, root); - flags |= RSTRING_NOEMBED | STR_SHARED; - } - - STR_SET_LEN(dup, RSTRING_LEN(str)); - if ((flags & ENCODING_MASK) == (ENCODING_INLINE_MAX<<ENCODING_SHIFT)) { encidx = rb_enc_get_index(str); flags &= ~ENCODING_MASK; @@ -1735,29 +1929,65 @@ str_duplicate_setup(VALUE klass, VALUE str, VALUE dup) return dup; } +static const VALUE flag_mask = ENC_CODERANGE_MASK | ENCODING_MASK | FL_FREEZE; + static inline VALUE -ec_str_duplicate(struct rb_execution_context_struct *ec, VALUE klass, VALUE str) +str_duplicate_setup_embed(VALUE klass, VALUE str, VALUE dup) { - VALUE dup; - if (FL_TEST(str, STR_NOEMBED)) { - dup = ec_str_alloc_heap(ec, klass); + VALUE flags = FL_TEST_RAW(str, flag_mask); + long len = RSTRING_LEN(str); + + RUBY_ASSERT(STR_EMBED_P(dup)); + RUBY_ASSERT(str_embed_capa(dup) >= len + TERM_LEN(str)); + MEMCPY(RSTRING(dup)->as.embed.ary, RSTRING(str)->as.embed.ary, char, len + TERM_LEN(str)); + STR_SET_LEN(dup, RSTRING_LEN(str)); + return str_duplicate_setup_encoding(str, dup, flags); +} + +static inline VALUE +str_duplicate_setup_heap(VALUE klass, VALUE str, VALUE dup) +{ + VALUE flags = FL_TEST_RAW(str, flag_mask); + VALUE root = str; + if (FL_TEST_RAW(str, STR_SHARED)) { + root = RSTRING(str)->as.heap.aux.shared; } - else { - dup = ec_str_alloc_embed(ec, klass, RSTRING_LEN(str) + TERM_LEN(str)); + else if (UNLIKELY(!OBJ_FROZEN_RAW(str))) { + root = str = str_new_frozen(klass, str); + flags = FL_TEST_RAW(str, flag_mask); } + RUBY_ASSERT(!STR_SHARED_P(root)); + RUBY_ASSERT(RB_OBJ_FROZEN_RAW(root)); - return str_duplicate_setup(klass, str, dup); + RSTRING(dup)->as.heap.ptr = RSTRING_PTR(str); + FL_SET_RAW(dup, RSTRING_NOEMBED); + STR_SET_SHARED(dup, root); + flags |= RSTRING_NOEMBED | STR_SHARED; + + STR_SET_LEN(dup, RSTRING_LEN(str)); + return str_duplicate_setup_encoding(str, dup, flags); +} + +static inline VALUE +str_duplicate_setup(VALUE klass, VALUE str, VALUE dup) +{ + if (STR_EMBED_P(str)) { + return str_duplicate_setup_embed(klass, str, dup); + } + else { + return str_duplicate_setup_heap(klass, str, dup); + } } static inline VALUE str_duplicate(VALUE klass, VALUE str) { VALUE dup; - if (FL_TEST(str, STR_NOEMBED)) { - dup = str_alloc_heap(klass); + if (STR_EMBED_P(str)) { + dup = str_alloc_embed(klass, RSTRING_LEN(str) + TERM_LEN(str)); } else { - dup = str_alloc_embed(klass, RSTRING_LEN(str) + TERM_LEN(str)); + dup = str_alloc_heap(klass); } return str_duplicate_setup(klass, str, dup); @@ -1769,6 +1999,18 @@ rb_str_dup(VALUE str) return str_duplicate(rb_obj_class(str), str); } +/* :nodoc: */ +VALUE +rb_str_dup_m(VALUE str) +{ + if (LIKELY(BARE_STRING_P(str))) { + return str_duplicate(rb_cString, str); + } + else { + return rb_obj_dup(str); + } +} + VALUE rb_str_resurrect(VALUE str) { @@ -1777,16 +2019,44 @@ rb_str_resurrect(VALUE str) } VALUE -rb_ec_str_resurrect(struct rb_execution_context_struct *ec, VALUE str) +rb_ec_str_resurrect(struct rb_execution_context_struct *ec, VALUE str, bool chilled) { RUBY_DTRACE_CREATE_HOOK(STRING, RSTRING_LEN(str)); - return ec_str_duplicate(ec, rb_cString, str); + VALUE new_str, klass = rb_cString; + + if (!(chilled && RTEST(rb_ivar_defined(str, id_debug_created_info))) && STR_EMBED_P(str)) { + new_str = ec_str_alloc_embed(ec, klass, RSTRING_LEN(str) + TERM_LEN(str)); + str_duplicate_setup_embed(klass, str, new_str); + } + else { + new_str = ec_str_alloc_heap(ec, klass); + str_duplicate_setup_heap(klass, str, new_str); + } + if (chilled) { + FL_SET_RAW(new_str, STR_CHILLED_LITERAL); + } + return new_str; +} + +VALUE +rb_str_with_debug_created_info(VALUE str, VALUE path, int line) +{ + VALUE debug_info = rb_ary_new_from_args(2, path, INT2FIX(line)); + if (OBJ_FROZEN_RAW(str)) str = rb_str_dup(str); + rb_ivar_set(str, id_debug_created_info, rb_ary_freeze(debug_info)); + FL_SET_RAW(str, STR_CHILLED_LITERAL); + return rb_str_freeze(str); } /* + * The documentation block below uses an include (instead of inline text) + * because the included text has non-ASCII characters (which are not allowed in a C file). + */ + +/* * * call-seq: - * String.new(string = '', **opts) -> new_string + * String.new(string = ''.encode(Encoding::ASCII_8BIT) , **options) -> new_string * * :include: doc/string/new.rdoc * @@ -1831,17 +2101,13 @@ rb_str_init(int argc, VALUE *argv, VALUE str) if (orig == str) n = 0; } str_modifiable(str); - if (STR_EMBED_P(str)) { /* make noembed always */ - char *new_ptr = ALLOC_N(char, (size_t)capa + termlen); - assert(RSTRING_LEN(str) + 1 <= str_embed_capa(str)); - memcpy(new_ptr, RSTRING(str)->as.embed.ary, RSTRING_LEN(str) + 1); - RSTRING(str)->as.heap.ptr = new_ptr; - } - else if (FL_TEST(str, STR_SHARED|STR_NOFREE)) { + if (STR_EMBED_P(str) || FL_TEST(str, STR_SHARED|STR_NOFREE)) { + /* make noembed always */ const size_t size = (size_t)capa + termlen; const char *const old_ptr = RSTRING_PTR(str); const size_t osize = RSTRING_LEN(str) + TERM_LEN(str); - char *new_ptr = ALLOC_N(char, (size_t)capa + termlen); + char *new_ptr = ALLOC_N(char, size); + if (STR_EMBED_P(str)) RUBY_ASSERT((long)osize <= str_embed_capa(str)); memcpy(new_ptr, old_ptr, osize < size ? osize : size); FL_UNSET_RAW(str, STR_SHARED|STR_NOFREE); RSTRING(str)->as.heap.ptr = new_ptr; @@ -1873,6 +2139,86 @@ rb_str_init(int argc, VALUE *argv, VALUE str) return str; } +/* :nodoc: */ +static VALUE +rb_str_s_new(int argc, VALUE *argv, VALUE klass) +{ + if (klass != rb_cString) { + return rb_class_new_instance_pass_kw(argc, argv, klass); + } + + static ID keyword_ids[2]; + VALUE orig, opt, encoding = Qnil, capacity = Qnil; + VALUE kwargs[2]; + rb_encoding *enc = NULL; + + int n = rb_scan_args(argc, argv, "01:", &orig, &opt); + if (NIL_P(opt)) { + return rb_class_new_instance_pass_kw(argc, argv, klass); + } + + keyword_ids[0] = rb_id_encoding(); + CONST_ID(keyword_ids[1], "capacity"); + rb_get_kwargs(opt, keyword_ids, 0, 2, kwargs); + encoding = kwargs[0]; + capacity = kwargs[1]; + + if (n == 1) { + orig = StringValue(orig); + } + else { + orig = Qnil; + } + + if (UNDEF_P(encoding)) { + if (!NIL_P(orig)) { + encoding = rb_obj_encoding(orig); + } + } + + if (!UNDEF_P(encoding)) { + enc = rb_to_encoding(encoding); + } + + // If capacity is nil, we're basically just duping `orig`. + if (UNDEF_P(capacity)) { + if (NIL_P(orig)) { + VALUE empty_str = str_new(klass, "", 0); + if (enc) { + rb_enc_associate(empty_str, enc); + } + return empty_str; + } + VALUE copy = str_duplicate(klass, orig); + rb_enc_associate(copy, enc); + ENC_CODERANGE_CLEAR(copy); + return copy; + } + + long capa = 0; + capa = NUM2LONG(capacity); + if (capa < 0) { + capa = 0; + } + + if (!NIL_P(orig)) { + long orig_capa = rb_str_capacity(orig); + if (orig_capa > capa) { + capa = orig_capa; + } + } + + VALUE str = str_enc_new(klass, NULL, capa, enc); + STR_SET_LEN(str, 0); + TERM_FILL(RSTRING_PTR(str), enc ? rb_enc_mbmaxlen(enc) : 1); + + if (!NIL_P(orig)) { + rb_str_buf_append(str, orig); + } + + return str; +} + #ifdef NONASCII_MASK #define is_utf8_lead_byte(c) (((c)&0xC0) != 0x80) @@ -2112,12 +2458,13 @@ rb_str_bytesize(VALUE str) * call-seq: * empty? -> true or false * - * Returns +true+ if the length of +self+ is zero, +false+ otherwise: + * Returns whether the length of +self+ is zero: * - * "hello".empty? # => false - * " ".empty? # => false - * "".empty? # => true + * 'hello'.empty? # => false + * ' '.empty? # => false + * ''.empty? # => true * + * Related: see {Querying}[rdoc-ref:String@Querying]. */ static VALUE @@ -2128,12 +2475,13 @@ rb_str_empty(VALUE str) /* * call-seq: - * string + other_string -> new_string + * self + other_string -> new_string * - * Returns a new \String containing +other_string+ concatenated to +self+: + * Returns a new string containing +other_string+ concatenated to +self+: * - * "Hello from " + self.to_s # => "Hello from main" + * 'Hello from ' + self.to_s # => "Hello from main" * + * Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. */ VALUE @@ -2153,7 +2501,7 @@ rb_str_plus(VALUE str1, VALUE str2) if (len1 > LONG_MAX - len2) { rb_raise(rb_eArgError, "string size too big"); } - str3 = str_new0(rb_cString, 0, len1+len2, termlen); + str3 = str_enc_new(rb_cString, 0, len1+len2, enc); ptr3 = RSTRING_PTR(str3); memcpy(ptr3, ptr1, len1); memcpy(ptr3+len1, ptr2, len2); @@ -2170,8 +2518,8 @@ rb_str_plus(VALUE str1, VALUE str2) VALUE rb_str_opt_plus(VALUE str1, VALUE str2) { - assert(RBASIC_CLASS(str1) == rb_cString); - assert(RBASIC_CLASS(str2) == rb_cString); + RUBY_ASSERT(RBASIC_CLASS(str1) == rb_cString); + RUBY_ASSERT(RBASIC_CLASS(str2) == rb_cString); long len1, len2; MAYBE_UNUSED(char) *ptr1, *ptr2; RSTRING_GETMEM(str1, ptr1, len1); @@ -2199,13 +2547,14 @@ rb_str_opt_plus(VALUE str1, VALUE str2) /* * call-seq: - * string * integer -> new_string + * self * n -> new_string * - * Returns a new \String containing +integer+ copies of +self+: + * Returns a new string containing +n+ copies of +self+: * - * "Ho! " * 3 # => "Ho! Ho! Ho! " - * "Ho! " * 0 # => "" + * 'Ho!' * 3 # => "Ho!Ho!Ho!" + * 'No!' * 0 # => "" * + * Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. */ VALUE @@ -2248,7 +2597,7 @@ rb_str_times(VALUE str, VALUE times) len *= RSTRING_LEN(str); termlen = TERM_LEN(str); - str2 = str_new0(rb_cString, 0, len, termlen); + str2 = str_enc_new(rb_cString, 0, len, STR_ENC_GET(str)); ptr2 = RSTRING_PTR(str2); if (len) { n = RSTRING_LEN(str); @@ -2268,20 +2617,22 @@ rb_str_times(VALUE str, VALUE times) /* * call-seq: - * string % object -> new_string + * self % object -> new_string * - * Returns the result of formatting +object+ into the format specification +self+ - * (see Kernel#sprintf for formatting details): + * Returns the result of formatting +object+ into the format specifications + * contained in +self+ + * (see {Format Specifications}[rdoc-ref:language/format_specifications.rdoc]): * - * "%05d" % 123 # => "00123" + * '%05d' % 123 # => "00123" * - * If +self+ contains multiple substitutions, +object+ must be - * an \Array or \Hash containing the values to be substituted: + * If +self+ contains multiple format specifications, + * +object+ must be an array or hash containing the objects to be formatted: * - * "%-5s: %016x" % [ "ID", self.object_id ] # => "ID : 00002b054ec93168" - * "foo = %{foo}" % {foo: 'bar'} # => "foo = bar" - * "foo = %{foo}, baz = %{baz}" % {foo: 'bar', baz: 'bat'} # => "foo = bar, baz = bat" + * '%-5s: %016x' % [ 'ID', self.object_id ] # => "ID : 00002b054ec93168" + * 'foo = %{foo}' % {foo: 'bar'} # => "foo = bar" + * 'foo = %{foo}, baz = %{baz}' % {foo: 'bar', baz: 'bat'} # => "foo = bar, baz = bat" * + * Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. */ static VALUE @@ -2303,34 +2654,54 @@ rb_check_lockedtmp(VALUE str) } } +// If none of these flags are set, we know we have an modifiable string. +// If any is set, we need to do more detailed checks. +#define STR_UNMODIFIABLE_MASK (FL_FREEZE | STR_TMPLOCK | STR_CHILLED) static inline void str_modifiable(VALUE str) { - rb_check_lockedtmp(str); - rb_check_frozen(str); + RUBY_ASSERT(ruby_thread_has_gvl_p()); + + if (RB_UNLIKELY(FL_ANY_RAW(str, STR_UNMODIFIABLE_MASK))) { + if (CHILLED_STRING_P(str)) { + CHILLED_STRING_MUTATED(str); + } + rb_check_lockedtmp(str); + rb_check_frozen(str); + } } static inline int str_dependent_p(VALUE str) { if (STR_EMBED_P(str) || !FL_TEST(str, STR_SHARED|STR_NOFREE)) { - return 0; + return FALSE; } else { - return 1; + return TRUE; } } +// If none of these flags are set, we know we have an independent string. +// If any is set, we need to do more detailed checks. +#define STR_DEPENDANT_MASK (STR_UNMODIFIABLE_MASK | STR_SHARED | STR_NOFREE) static inline int str_independent(VALUE str) { - str_modifiable(str); - return !str_dependent_p(str); + RUBY_ASSERT(ruby_thread_has_gvl_p()); + + if (RB_UNLIKELY(FL_ANY_RAW(str, STR_DEPENDANT_MASK))) { + str_modifiable(str); + return !str_dependent_p(str); + } + return TRUE; } static void str_make_independent_expand(VALUE str, long len, long expand, const int termlen) { + RUBY_ASSERT(ruby_thread_has_gvl_p()); + char *ptr; char *oldptr; long capa = len + expand; @@ -2373,6 +2744,8 @@ rb_str_modify(VALUE str) void rb_str_modify_expand(VALUE str, long expand) { + RUBY_ASSERT(ruby_thread_has_gvl_p()); + int termlen = TERM_LEN(str); long len = RSTRING_LEN(str); @@ -2417,10 +2790,17 @@ str_discard(VALUE str) void rb_must_asciicompat(VALUE str) { - rb_encoding *enc = rb_enc_get(str); - if (!enc) { + int encindex = rb_enc_get_index(str); + + if (RB_UNLIKELY(encindex == -1)) { rb_raise(rb_eTypeError, "not encoding capable object"); } + + if (RB_LIKELY(str_encindex_fastpath(encindex))) { + return; + } + + rb_encoding *enc = rb_enc_from_index(encindex); if (!rb_enc_asciicompat(enc)) { rb_raise(rb_eEncCompatError, "ASCII incompatible encoding: %s", rb_enc_name(enc)); } @@ -2429,6 +2809,8 @@ rb_must_asciicompat(VALUE str) VALUE rb_string_value(volatile VALUE *ptr) { + RUBY_ASSERT(ruby_thread_has_gvl_p()); + VALUE s = *ptr; if (!RB_TYPE_P(s, T_STRING)) { s = rb_str_to_str(s); @@ -2487,7 +2869,7 @@ rb_str_change_terminator_length(VALUE str, const int oldtermlen, const int terml long capa = str_capacity(str, oldtermlen) + oldtermlen; long len = RSTRING_LEN(str); - assert(capa >= len); + RUBY_ASSERT(capa >= len); if (capa - len < termlen) { rb_check_lockedtmp(str); str_make_independent_expand(str, len, 0L, termlen); @@ -2499,7 +2881,7 @@ rb_str_change_terminator_length(VALUE str, const int oldtermlen, const int terml else { if (!STR_EMBED_P(str)) { /* modify capa instead of realloc */ - assert(!FL_TEST((str), STR_SHARED)); + RUBY_ASSERT(!FL_TEST((str), STR_SHARED)); RSTRING(str)->as.heap.aux.capa = capa - termlen; } if (termlen > oldtermlen) { @@ -2576,14 +2958,16 @@ rb_check_string_type(VALUE str) * call-seq: * String.try_convert(object) -> object, new_string, or nil * - * If +object+ is a \String object, returns +object+. + * Attempts to convert the given +object+ to a string. + * + * If +object+ is already a string, returns +object+, unmodified. * * Otherwise if +object+ responds to <tt>:to_str</tt>, * calls <tt>object.to_str</tt> and returns the result. * * Returns +nil+ if +object+ does not respond to <tt>:to_str</tt>. * - * Raises an exception unless <tt>object.to_str</tt> returns a \String object. + * Raises an exception unless <tt>object.to_str</tt> returns a string. */ static VALUE rb_str_s_try_convert(VALUE dummy, VALUE str) @@ -2732,9 +3116,9 @@ str_subseq(VALUE str, long beg, long len) { VALUE str2; - assert(beg >= 0); - assert(len >= 0); - assert(beg+len <= RSTRING_LEN(str)); + RUBY_ASSERT(beg >= 0); + RUBY_ASSERT(len >= 0); + RUBY_ASSERT(beg+len <= RSTRING_LEN(str)); const int termlen = TERM_LEN(str); if (!SHARABLE_SUBSTRING_P(beg, len, RSTRING_LEN(str))) { @@ -2755,7 +3139,7 @@ str_subseq(VALUE str, long beg, long len) } else { str_replace_shared(str2, str); - assert(!STR_EMBED_P(str2)); + RUBY_ASSERT(!STR_EMBED_P(str2)); ENC_CODERANGE_CLEAR(str2); RSTRING(str2)->as.heap.ptr += beg; if (RSTRING_LEN(str2) > len) { @@ -2779,11 +3163,12 @@ rb_str_subpos(VALUE str, long beg, long *lenp) { long len = *lenp; long slen = -1L; - long blen = RSTRING_LEN(str); + const long blen = RSTRING_LEN(str); rb_encoding *enc = STR_ENC_GET(str); char *p, *s = RSTRING_PTR(str), *e = s + blen; if (len < 0) return 0; + if (beg < 0 && -beg < 0) return 0; if (!blen) { len = 0; } @@ -2801,7 +3186,8 @@ rb_str_subpos(VALUE str, long beg, long *lenp) } if (beg < 0) { if (len > -beg) len = -beg; - if (-beg * rb_enc_mbmaxlen(enc) < RSTRING_LEN(str) / 8) { + if ((ENC_CODERANGE(str) == ENC_CODERANGE_VALID) && + (-beg * rb_enc_mbmaxlen(enc) < blen / 8)) { beg = -beg; while (beg-- > len && (e = rb_enc_prev_char(s, e, e, enc)) != 0); p = e; @@ -2819,7 +3205,7 @@ rb_str_subpos(VALUE str, long beg, long *lenp) if (len == 0) goto end; } } - else if (beg > 0 && beg > RSTRING_LEN(str)) { + else if (beg > 0 && beg > blen) { return 0; } if (len == 0) { @@ -2867,6 +3253,12 @@ rb_str_substr(VALUE str, long beg, long len) return str_substr(str, beg, len, TRUE); } +VALUE +rb_str_substr_two_fixnums(VALUE str, VALUE beg, VALUE len, int empty) +{ + return str_substr(str, NUM2LONG(beg), NUM2LONG(len), empty); +} + static VALUE str_substr(VALUE str, long beg, long len, int empty) { @@ -2886,24 +3278,30 @@ str_substr(VALUE str, long beg, long len, int empty) VALUE rb_str_freeze(VALUE str) { + if (CHILLED_STRING_P(str)) { + FL_UNSET_RAW(str, STR_CHILLED); + } + if (OBJ_FROZEN(str)) return str; rb_str_resize(str, RSTRING_LEN(str)); return rb_obj_freeze(str); } - /* * call-seq: * +string -> new_string or self * - * Returns +self+ if +self+ is not frozen. + * Returns +self+ if +self+ is not frozen and can be mutated + * without warning issuance. * * Otherwise returns <tt>self.dup</tt>, which is not frozen. + * + * Related: see {Freezing/Unfreezing}[rdoc-ref:String@Freezing-2FUnfreezing]. */ static VALUE str_uplus(VALUE str) { - if (OBJ_FROZEN(str)) { + if (OBJ_FROZEN(str) || CHILLED_STRING_P(str)) { return rb_str_dup(str); } else { @@ -2913,24 +3311,37 @@ str_uplus(VALUE str) /* * call-seq: - * -string -> frozen_string - * dedup -> frozen_string + * -self -> frozen_string * - * Returns a frozen, possibly pre-existing copy of the string. + * Returns a frozen string equal to +self+. * - * The returned \String will be deduplicated as long as it does not have - * any instance variables set on it and is not a String subclass. + * The returned string is +self+ if and only if all of the following are true: * - * Note that <tt>-string</tt> variant is more convenient for defining - * constants: + * - +self+ is already frozen. + * - +self+ is an instance of \String (rather than of a subclass of \String) + * - +self+ has no instance variables set on it. * - * FILENAME = -'config/database.yml' + * Otherwise, the returned string is a frozen copy of +self+. * - * while +dedup+ is better suitable for using the method in chains - * of calculations: + * Returning +self+, when possible, saves duplicating +self+; + * see {Data deduplication}[https://en.wikipedia.org/wiki/Data_deduplication]. * - * @url_list.concat(urls.map(&:dedup)) + * It may also save duplicating other, already-existing, strings: * + * s0 = 'foo' + * s1 = 'foo' + * s0.object_id == s1.object_id # => false + * (-s0).object_id == (-s1).object_id # => true + * + * Note that method #-@ is convenient for defining a constant: + * + * FileName = -'config/database.yml' + * + * While its alias #dedup is better suited for chaining: + * + * 'foo'.dedup.gsub!('o') + * + * Related: see {Freezing/Unfreezing}[rdoc-ref:String@Freezing-2FUnfreezing]. */ static VALUE str_uminus(VALUE str) @@ -2947,6 +3358,7 @@ RUBY_ALIAS_FUNCTION(rb_str_dup_frozen(VALUE str), rb_str_new_frozen, (str)) VALUE rb_str_locktmp(VALUE str) { + rb_check_frozen(str); if (FL_TEST(str, STR_TMPLOCK)) { rb_raise(rb_eRuntimeError, "temporal locking already locked string"); } @@ -2957,6 +3369,7 @@ rb_str_locktmp(VALUE str) VALUE rb_str_unlocktmp(VALUE str) { + rb_check_frozen(str); if (!FL_TEST(str, STR_TMPLOCK)) { rb_raise(rb_eRuntimeError, "temporal unlocking already unlocked string"); } @@ -2964,7 +3377,7 @@ rb_str_unlocktmp(VALUE str) return str; } -RUBY_FUNC_EXPORTED VALUE +VALUE rb_str_locktmp_ensure(VALUE str, VALUE (*func)(VALUE), VALUE arg) { rb_str_locktmp(str); @@ -2974,6 +3387,8 @@ rb_str_locktmp_ensure(VALUE str, VALUE (*func)(VALUE), VALUE arg) void rb_str_set_len(VALUE str, long len) { + RUBY_ASSERT(ruby_thread_has_gvl_p()); + long capa; const int termlen = TERM_LEN(str); @@ -2984,6 +3399,37 @@ rb_str_set_len(VALUE str, long len) if (len > (capa = (long)str_capacity(str, termlen)) || len < 0) { rb_bug("probable buffer overflow: %ld for %ld", len, capa); } + + int cr = ENC_CODERANGE(str); + if (len == 0) { + /* Empty string does not contain non-ASCII */ + ENC_CODERANGE_SET(str, ENC_CODERANGE_7BIT); + } + else if (cr == ENC_CODERANGE_UNKNOWN) { + /* Leave unknown. */ + } + else if (len > RSTRING_LEN(str)) { + if (ENC_CODERANGE_CLEAN_P(cr)) { + /* Update the coderange regarding the extended part. */ + const char *const prev_end = RSTRING_END(str); + const char *const new_end = RSTRING_PTR(str) + len; + rb_encoding *enc = rb_enc_get(str); + rb_str_coderange_scan_restartable(prev_end, new_end, enc, &cr); + ENC_CODERANGE_SET(str, cr); + } + else if (cr == ENC_CODERANGE_BROKEN) { + /* May be valid now, by appended part. */ + ENC_CODERANGE_SET(str, ENC_CODERANGE_UNKNOWN); + } + } + else if (len < RSTRING_LEN(str)) { + if (cr != ENC_CODERANGE_7BIT) { + /* ASCII-only string is keeping after truncated. Valid + * and broken may be invalid or valid, leave unknown. */ + ENC_CODERANGE_SET(str, ENC_CODERANGE_UNKNOWN); + } + } + STR_SET_LEN(str, len); TERM_FILL(&RSTRING_PTR(str)[len], termlen); } @@ -2997,14 +3443,14 @@ rb_str_resize(VALUE str, long len) int independent = str_independent(str); long slen = RSTRING_LEN(str); + const int termlen = TERM_LEN(str); - if (slen > len && ENC_CODERANGE(str) != ENC_CODERANGE_7BIT) { + if (slen > len || (termlen != 1 && slen < len)) { ENC_CODERANGE_CLEAR(str); } { long capa; - const int termlen = TERM_LEN(str); if (STR_EMBED_P(str)) { if (len == slen) return str; if (str_embed_capa(str) >= len + termlen) { @@ -3041,6 +3487,32 @@ rb_str_resize(VALUE str, long len) return str; } +static void +str_ensure_available_capa(VALUE str, long len) +{ + str_modify_keep_cr(str); + + const int termlen = TERM_LEN(str); + long olen = RSTRING_LEN(str); + + if (RB_UNLIKELY(olen > LONG_MAX - len)) { + rb_raise(rb_eArgError, "string sizes too big"); + } + + long total = olen + len; + long capa = str_capacity(str, termlen); + + if (capa < total) { + if (total >= LONG_MAX / 2) { + capa = total; + } + while (total > capa) { + capa = 2 * capa + termlen; /* == 2*(capa+termlen)-termlen */ + } + RESIZE_CAPA_TERM(str, capa, termlen); + } +} + static VALUE str_buf_cat4(VALUE str, const char *ptr, long len, bool keep_cr) { @@ -3107,6 +3579,62 @@ rb_str_cat_cstr(VALUE str, const char *ptr) return rb_str_buf_cat(str, ptr, strlen(ptr)); } +static void +rb_str_buf_cat_byte(VALUE str, unsigned char byte) +{ + RUBY_ASSERT(RB_ENCODING_GET_INLINED(str) == ENCINDEX_ASCII_8BIT || RB_ENCODING_GET_INLINED(str) == ENCINDEX_US_ASCII); + + // We can't write directly to shared strings without impacting others, so we must make the string independent. + if (UNLIKELY(!str_independent(str))) { + str_make_independent(str); + } + + long string_length = -1; + const int null_terminator_length = 1; + char *sptr; + RSTRING_GETMEM(str, sptr, string_length); + + // Ensure the resulting string wouldn't be too long. + if (UNLIKELY(string_length > LONG_MAX - 1)) { + rb_raise(rb_eArgError, "string sizes too big"); + } + + long string_capacity = str_capacity(str, null_terminator_length); + + // Get the code range before any modifications since those might clear the code range. + int cr = ENC_CODERANGE(str); + + // Check if the string has spare string_capacity to write the new byte. + if (LIKELY(string_capacity >= string_length + 1)) { + // In fast path we can write the new byte and note the string's new length. + sptr[string_length] = byte; + STR_SET_LEN(str, string_length + 1); + TERM_FILL(sptr + string_length + 1, null_terminator_length); + } + else { + // If there's not enough string_capacity, make a call into the general string concatenation function. + str_buf_cat(str, (char *)&byte, 1); + } + + // If the code range is already known, we can derive the resulting code range cheaply by looking at the byte we + // just appended. If the code range is unknown, but the string was empty, then we can also derive the code range + // by looking at the byte we just appended. Otherwise, we'd have to scan the bytes to determine the code range so + // we leave it as unknown. It cannot be broken for binary strings so we don't need to handle that option. + if (cr == ENC_CODERANGE_7BIT || string_length == 0) { + if (ISASCII(byte)) { + ENC_CODERANGE_SET(str, ENC_CODERANGE_7BIT); + } + else { + ENC_CODERANGE_SET(str, ENC_CODERANGE_VALID); + + // Promote a US-ASCII string to ASCII-8BIT when a non-ASCII byte is appended. + if (UNLIKELY(RB_ENCODING_GET_INLINED(str) == ENCINDEX_US_ASCII)) { + rb_enc_associate_index(str, ENCINDEX_ASCII_8BIT); + } + } + } +} + RUBY_ALIAS_FUNCTION(rb_str_buf_cat(VALUE str, const char *ptr, long len), rb_str_cat, (str, ptr, len)) RUBY_ALIAS_FUNCTION(rb_str_buf_cat2(VALUE str, const char *ptr), rb_str_cat_cstr, (str, ptr)) RUBY_ALIAS_FUNCTION(rb_str_cat2(VALUE str, const char *ptr), rb_str_cat_cstr, (str, ptr)) @@ -3197,7 +3725,7 @@ rb_enc_cr_str_buf_cat(VALUE str, const char *ptr, long len, incompatible: rb_raise(rb_eEncCompatError, "incompatible character encodings: %s and %s", - rb_enc_name(str_enc), rb_enc_name(ptr_enc)); + rb_enc_inspect_name(str_enc), rb_enc_inspect_name(ptr_enc)); UNREACHABLE_RETURN(Qundef); } @@ -3242,6 +3770,7 @@ rb_str_buf_append(VALUE str, VALUE str2) case ENC_CODERANGE_7BIT: // If RHS is 7bit we can do simple concatenation str_buf_cat4(str, RSTRING_PTR(str2), RSTRING_LEN(str2), true); + RB_GC_GUARD(str2); return str; case ENC_CODERANGE_VALID: // If RHS is valid, we can do simple concatenation if encodings are the same @@ -3251,6 +3780,7 @@ rb_str_buf_append(VALUE str, VALUE str2) if (UNLIKELY(str_cr != ENC_CODERANGE_VALID)) { ENC_CODERANGE_SET(str, RB_ENC_CODERANGE_AND(str_cr, str2_cr)); } + RB_GC_GUARD(str2); return str; } } @@ -3302,19 +3832,7 @@ rb_str_concat_literals(size_t num, const VALUE *strary) * call-seq: * concat(*objects) -> string * - * Concatenates each object in +objects+ to +self+ and returns +self+: - * - * s = 'foo' - * s.concat('bar', 'baz') # => "foobarbaz" - * s # => "foobarbaz" - * - * For each given object +object+ that is an \Integer, - * the value is considered a codepoint and converted to a character before concatenation: - * - * s = 'foo' - * s.concat(32, 'bar', 32, 'baz') # => "foo bar baz" - * - * Related: String#<<, which takes a single argument. + * :include: doc/string/concat.rdoc */ static VALUE rb_str_concat_multi(int argc, VALUE *argv, VALUE str) @@ -3339,21 +3857,179 @@ rb_str_concat_multi(int argc, VALUE *argv, VALUE str) /* * call-seq: - * string << object -> string + * append_as_bytes(*objects) -> self + * + * Concatenates each object in +objects+ into +self+; returns +self+; + * performs no encoding validation or conversion: + * + * s = 'foo' + * s.append_as_bytes(" \xE2\x82") # => "foo \xE2\x82" + * s.valid_encoding? # => false + * s.append_as_bytes("\xAC 12") + * s.valid_encoding? # => true + * + * When a given object is an integer, + * the value is considered an 8-bit byte; + * if the integer occupies more than one byte (i.e,. is greater than 255), + * appends only the low-order byte (similar to String#setbyte): + * + * s = "" + * s.append_as_bytes(0, 257) # => "\u0000\u0001" + * s.bytesize # => 2 + * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. + */ + +VALUE +rb_str_append_as_bytes(int argc, VALUE *argv, VALUE str) +{ + long needed_capacity = 0; + volatile VALUE t0; + enum ruby_value_type *types = ALLOCV_N(enum ruby_value_type, t0, argc); + + for (int index = 0; index < argc; index++) { + VALUE obj = argv[index]; + enum ruby_value_type type = types[index] = rb_type(obj); + switch (type) { + case T_FIXNUM: + case T_BIGNUM: + needed_capacity++; + break; + case T_STRING: + needed_capacity += RSTRING_LEN(obj); + break; + default: + rb_raise( + rb_eTypeError, + "wrong argument type %"PRIsVALUE" (expected String or Integer)", + rb_obj_class(obj) + ); + break; + } + } + + str_ensure_available_capa(str, needed_capacity); + char *sptr = RSTRING_END(str); + + for (int index = 0; index < argc; index++) { + VALUE obj = argv[index]; + enum ruby_value_type type = types[index]; + switch (type) { + case T_FIXNUM: + case T_BIGNUM: { + argv[index] = obj = rb_int_and(obj, INT2FIX(0xff)); + char byte = (char)(NUM2INT(obj) & 0xFF); + *sptr = byte; + sptr++; + break; + } + case T_STRING: { + const char *ptr; + long len; + RSTRING_GETMEM(obj, ptr, len); + memcpy(sptr, ptr, len); + sptr += len; + break; + } + default: + rb_bug("append_as_bytes arguments should have been validated"); + } + } + + STR_SET_LEN(str, RSTRING_LEN(str) + needed_capacity); + TERM_FILL(sptr, TERM_LEN(str)); /* sentinel */ + + int cr = ENC_CODERANGE(str); + switch (cr) { + case ENC_CODERANGE_7BIT: { + for (int index = 0; index < argc; index++) { + VALUE obj = argv[index]; + enum ruby_value_type type = types[index]; + switch (type) { + case T_FIXNUM: + case T_BIGNUM: { + if (!ISASCII(NUM2INT(obj))) { + goto clear_cr; + } + break; + } + case T_STRING: { + if (ENC_CODERANGE(obj) != ENC_CODERANGE_7BIT) { + goto clear_cr; + } + break; + } + default: + rb_bug("append_as_bytes arguments should have been validated"); + } + } + break; + } + case ENC_CODERANGE_VALID: + if (ENCODING_GET_INLINED(str) == ENCINDEX_ASCII_8BIT) { + goto keep_cr; + } + else { + goto clear_cr; + } + break; + default: + goto clear_cr; + break; + } + + RB_GC_GUARD(t0); + + clear_cr: + // If no fast path was hit, we clear the coderange. + // append_as_bytes is predominantly meant to be used in + // buffering situation, hence it's likely the coderange + // will never be scanned, so it's not worth spending time + // precomputing the coderange except for simple and common + // situations. + ENC_CODERANGE_CLEAR(str); + keep_cr: + return str; +} + +/* + * call-seq: + * self << object -> self + * + * Appends a string representation of +object+ to +self+; + * returns +self+. * - * Concatenates +object+ to +self+ and returns +self+: + * If +object+ is a string, appends it to +self+: * * s = 'foo' * s << 'bar' # => "foobar" * s # => "foobar" * - * If +object+ is an \Integer, - * the value is considered a codepoint and converted to a character before concatenation: + * If +object+ is an integer, + * its value is considered a codepoint; + * converts the value to a character before concatenating: * * s = 'foo' * s << 33 # => "foo!" * - * Related: String#concat, which takes multiple arguments. + * Additionally, if the codepoint is in range <tt>0..0xff</tt> + * and the encoding of +self+ is Encoding::US_ASCII, + * changes the encoding to Encoding::ASCII_8BIT: + * + * s = 'foo'.encode(Encoding::US_ASCII) + * s.encoding # => #<Encoding:US-ASCII> + * s << 0xff # => "foo\xFF" + * s.encoding # => #<Encoding:BINARY (ASCII-8BIT)> + * + * Raises RangeError if that codepoint is not representable in the encoding of +self+: + * + * s = 'foo' + * s.encoding # => <Encoding:UTF-8> + * s << 0x00110000 # 1114112 out of char range (RangeError) + * s = 'foo'.encode(Encoding::EUC_JP) + * s << 0x00800080 # invalid codepoint 0x800080 in EUC-JP (RangeError) + * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ VALUE rb_str_concat(VALUE str1, VALUE str2) @@ -3377,14 +4053,9 @@ rb_str_concat(VALUE str1, VALUE str2) } encidx = rb_ascii8bit_appendable_encoding_index(enc, code); + if (encidx >= 0) { - char buf[1]; - buf[0] = (char)code; - rb_str_cat(str1, buf, 1); - if (encidx != rb_enc_to_index(enc)) { - rb_enc_associate_index(str1, encidx); - ENC_CODERANGE_SET(str1, ENC_CODERANGE_VALID); - } + rb_str_buf_cat_byte(str1, (unsigned char)code); } else { long pos = RSTRING_LEN(str1); @@ -3408,8 +4079,12 @@ rb_str_concat(VALUE str1, VALUE str2) } rb_str_resize(str1, pos+len); memcpy(RSTRING_PTR(str1) + pos, buf, len); - if (cr == ENC_CODERANGE_7BIT && code > 127) + if (cr == ENC_CODERANGE_7BIT && code > 127) { cr = ENC_CODERANGE_VALID; + } + else if (cr == ENC_CODERANGE_BROKEN) { + cr = ENC_CODERANGE_UNKNOWN; + } ENC_CODERANGE_SET(str1, cr); } return str1; @@ -3437,15 +4112,14 @@ rb_ascii8bit_appendable_encoding_index(rb_encoding *enc, unsigned int code) /* * call-seq: - * prepend(*other_strings) -> string + * prepend(*other_strings) -> new_string * - * Prepends each string in +other_strings+ to +self+ and returns +self+: + * Prefixes to +self+ the concatenation of the given +other_strings+; returns +self+: * - * s = 'foo' - * s.prepend('bar', 'baz') # => "barbazfoo" - * s # => "barbazfoo" + * 'baz'.prepend('foo', 'bar') # => "foobarbaz" + * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. * - * Related: String#concat. */ static VALUE @@ -3472,11 +4146,15 @@ rb_str_prepend_multi(int argc, VALUE *argv, VALUE str) st_index_t rb_str_hash(VALUE str) { - int e = ENCODING_GET(str); - if (e && is_ascii_string(str)) { - e = 0; + if (FL_TEST_RAW(str, STR_PRECOMPUTED_HASH)) { + st_index_t precomputed_hash; + memcpy(&precomputed_hash, RSTRING_END(str) + TERM_LEN(str), sizeof(precomputed_hash)); + + RUBY_ASSERT(precomputed_hash == str_do_hash(str)); + return precomputed_hash; } - return rb_memhash((const void *)RSTRING_PTR(str), RSTRING_LEN(str)) ^ e; + + return str_do_hash(str); } int @@ -3495,10 +4173,8 @@ rb_str_hash_cmp(VALUE str1, VALUE str2) * call-seq: * hash -> integer * - * Returns the integer hash value for +self+. - * The value is based on the length, content and encoding of +self+. + * :include: doc/string/hash.rdoc * - * Related: Object#hash. */ static VALUE @@ -3563,22 +4239,29 @@ rb_str_cmp(VALUE str1, VALUE str2) /* * call-seq: - * string == object -> true or false - * string === object -> true or false + * self == other -> true or false + * + * Returns whether +other+ is equal to +self+. * - * Returns +true+ if +object+ has the same length and content; - * as +self+; +false+ otherwise: + * When +other+ is a string, returns whether +other+ has the same length and content as +self+: * * s = 'foo' - * s == 'foo' # => true + * s == 'foo' # => true * s == 'food' # => false - * s == 'FOO' # => false + * s == 'FOO' # => false * * Returns +false+ if the two strings' encodings are not compatible: - * "\u{e4 f6 fc}".encode("ISO-8859-1") == ("\u{c4 d6 dc}") # => false * - * If +object+ is not an instance of \String but responds to +to_str+, then the - * two strings are compared using <code>object.==</code>. + * "\u{e4 f6 fc}".encode(Encoding::ISO_8859_1) == ("\u{c4 d6 dc}") # => false + * + * When +other+ is not a string: + * + * - If +other+ responds to method <tt>to_str</tt>, + * <tt>other == self</tt> is called and its return value is returned. + * - If +other+ does not respond to <tt>to_str</tt>, + * +false+ is returned. + * + * Related: {Comparing}[rdoc-ref:String@Comparing]. */ VALUE @@ -3598,17 +4281,7 @@ rb_str_equal(VALUE str1, VALUE str2) * call-seq: * eql?(object) -> true or false * - * Returns +true+ if +object+ has the same length and content; - * as +self+; +false+ otherwise: - * - * s = 'foo' - * s.eql?('foo') # => true - * s.eql?('food') # => false - * s.eql?('FOO') # => false - * - * Returns +false+ if the two strings' encodings are not compatible: - * - * "\u{e4 f6 fc}".encode("ISO-8859-1").eql?("\u{c4 d6 dc}") # => false + * :include: doc/string/eql_p.rdoc * */ @@ -3622,24 +4295,31 @@ rb_str_eql(VALUE str1, VALUE str2) /* * call-seq: - * string <=> other_string -> -1, 0, 1, or nil + * self <=> other -> -1, 0, 1, or nil * - * Compares +self+ and +other_string+, returning: + * Compares +self+ and +other+, + * evaluating their _contents_, not their _lengths_. * - * - -1 if +other_string+ is larger. - * - 0 if the two are equal. - * - 1 if +other_string+ is smaller. - * - +nil+ if the two are incomparable. + * Returns: + * + * - +-1+, if +self+ is smaller. + * - +0+, if the two are equal. + * - +1+, if +self+ is larger. + * - +nil+, if the two are incomparable. * * Examples: * - * 'foo' <=> 'foo' # => 0 - * 'foo' <=> 'food' # => -1 - * 'food' <=> 'foo' # => 1 - * 'FOO' <=> 'foo' # => -1 - * 'foo' <=> 'FOO' # => 1 - * 'foo' <=> 1 # => nil + * 'a' <=> 'b' # => -1 + * 'a' <=> 'ab' # => -1 + * 'a' <=> 'a' # => 0 + * 'b' <=> 'a' # => 1 + * 'ab' <=> 'a' # => 1 + * 'a' <=> :a # => nil * + * \Class \String includes module Comparable, + * each of whose methods uses String#<=> for comparison. + * + * Related: see {Comparing}[rdoc-ref:String@Comparing]. */ static VALUE @@ -3661,26 +4341,26 @@ static VALUE str_casecmp_p(VALUE str1, VALUE str2); * call-seq: * casecmp(other_string) -> -1, 0, 1, or nil * - * Compares <tt>self.downcase</tt> and <tt>other_string.downcase</tt>; returns: + * Ignoring case, compares +self+ and +other_string+; returns: * - * - -1 if <tt>other_string.downcase</tt> is larger. + * - -1 if <tt>self.downcase</tt> is smaller than <tt>other_string.downcase</tt>. * - 0 if the two are equal. - * - 1 if <tt>other_string.downcase</tt> is smaller. + * - 1 if <tt>self.downcase</tt> is larger than <tt>other_string.downcase</tt>. * - +nil+ if the two are incomparable. * + * See {Case Mapping}[rdoc-ref:case_mapping.rdoc]. + * * Examples: * - * 'foo'.casecmp('foo') # => 0 + * 'foo'.casecmp('goo') # => -1 + * 'goo'.casecmp('foo') # => 1 * 'foo'.casecmp('food') # => -1 * 'food'.casecmp('foo') # => 1 - * 'FOO'.casecmp('foo') # => 0 - * 'foo'.casecmp('FOO') # => 0 - * 'foo'.casecmp(1) # => nil - * - * See {Case Mapping}[rdoc-ref:case_mapping.rdoc]. - * - * Related: String#casecmp?. + * 'FOO'.casecmp('foo') # => 0 + * 'foo'.casecmp('FOO') # => 0 + * 'foo'.casecmp(1) # => nil * + * Related: see {Comparing}[rdoc-ref:String@Comparing]. */ static VALUE @@ -3745,9 +4425,9 @@ str_casecmp(VALUE str1, VALUE str2) p2 += l2; } } - if (RSTRING_LEN(str1) == RSTRING_LEN(str2)) return INT2FIX(0); - if (RSTRING_LEN(str1) > RSTRING_LEN(str2)) return INT2FIX(1); - return INT2FIX(-1); + if (p1 == p1end && p2 == p2end) return INT2FIX(0); + if (p1 == p1end) return INT2FIX(-1); + return INT2FIX(1); } /* @@ -3755,22 +4435,21 @@ str_casecmp(VALUE str1, VALUE str2) * casecmp?(other_string) -> true, false, or nil * * Returns +true+ if +self+ and +other_string+ are equal after - * Unicode case folding, otherwise +false+: - * - * 'foo'.casecmp?('foo') # => true - * 'foo'.casecmp?('food') # => false - * 'food'.casecmp?('foo') # => false - * 'FOO'.casecmp?('foo') # => true - * 'foo'.casecmp?('FOO') # => true - * - * Returns +nil+ if the two values are incomparable: - * - * 'foo'.casecmp?(1) # => nil + * Unicode case folding, +false+ if unequal, +nil+ if incomparable. * * See {Case Mapping}[rdoc-ref:case_mapping.rdoc]. * - * Related: String#casecmp. + * Examples: * + * 'foo'.casecmp?('goo') # => false + * 'goo'.casecmp?('foo') # => false + * 'foo'.casecmp?('food') # => false + * 'food'.casecmp?('foo') # => false + * 'FOO'.casecmp?('foo') # => true + * 'foo'.casecmp?('FOO') # => true + * 'foo'.casecmp?(1) # => nil + * + * Related: see {Comparing}[rdoc-ref:String@Comparing]. */ static VALUE @@ -3866,8 +4545,7 @@ rb_strseq_index(VALUE str, VALUE sub, long offset, int in_byte) /* * call-seq: - * index(substring, offset = 0) -> integer or nil - * index(regexp, offset = 0) -> integer or nil + * index(pattern, offset = 0) -> integer or nil * * :include: doc/string/index.rdoc * @@ -3924,55 +4602,82 @@ rb_str_index_m(int argc, VALUE *argv, VALUE str) static void str_ensure_byte_pos(VALUE str, long pos) { - const char *s = RSTRING_PTR(str); - const char *e = RSTRING_END(str); - const char *p = s + pos; - const char *pp = rb_enc_left_char_head(s, p, e, rb_enc_get(str)); - if (p != pp) { - rb_raise(rb_eIndexError, - "offset %ld does not land on character boundary", pos); + if (!single_byte_optimizable(str)) { + const char *s = RSTRING_PTR(str); + const char *e = RSTRING_END(str); + const char *p = s + pos; + if (!at_char_boundary(s, p, e, rb_enc_get(str))) { + rb_raise(rb_eIndexError, + "offset %ld does not land on character boundary", pos); + } } } /* * call-seq: - * byteindex(substring, offset = 0) -> integer or nil - * byteindex(regexp, offset = 0) -> integer or nil + * byteindex(object, offset = 0) -> integer or nil + * + * Returns the 0-based integer index of a substring of +self+ + * specified by +object+ (a string or Regexp) and +offset+, + * or +nil+ if there is no such substring; + * the returned index is the count of _bytes_ (not characters). * - * Returns the \Integer byte-based index of the first occurrence of the given +substring+, - * or +nil+ if none found: + * When +object+ is a string, + * returns the index of the first found substring equal to +object+: * - * 'foo'.byteindex('f') # => 0 - * 'foo'.byteindex('o') # => 1 - * 'foo'.byteindex('oo') # => 1 - * 'foo'.byteindex('ooo') # => nil + * s = 'foo' # => "foo" + * s.size # => 3 # Three 1-byte characters. + * s.bytesize # => 3 # Three bytes. + * s.byteindex('f') # => 0 + * s.byteindex('o') # => 1 + * s.byteindex('oo') # => 1 + * s.byteindex('ooo') # => nil * - * Returns the \Integer byte-based index of the first match for the given \Regexp +regexp+, - * or +nil+ if none found: + * When +object+ is a Regexp, + * returns the index of the first found substring matching +object+; + * updates {Regexp-related global variables}[rdoc-ref:Regexp@Global+Variables]: * - * 'foo'.byteindex(/f/) # => 0 - * 'foo'.byteindex(/o/) # => 1 - * 'foo'.byteindex(/oo/) # => 1 - * 'foo'.byteindex(/ooo/) # => nil + * s = 'foo' + * s.byteindex(/f/) # => 0 + * $~ # => #<MatchData "f"> + * s.byteindex(/o/) # => 1 + * s.byteindex(/oo/) # => 1 + * s.byteindex(/ooo/) # => nil + * $~ # => nil + * + * \Integer argument +offset+, if given, specifies the 0-based index + * of the byte where searching is to begin. * - * \Integer argument +offset+, if given, specifies the byte-based position in the - * string to begin the search: + * When +offset+ is non-negative, + * searching begins at byte position +offset+: + * + * s = 'foo' + * s.byteindex('o', 1) # => 1 + * s.byteindex('o', 2) # => 2 + * s.byteindex('o', 3) # => nil * - * 'foo'.byteindex('o', 1) # => 1 - * 'foo'.byteindex('o', 2) # => 2 - * 'foo'.byteindex('o', 3) # => nil + * When +offset+ is negative, counts backward from the end of +self+: * - * If +offset+ is negative, counts backward from the end of +self+: + * s = 'foo' + * s.byteindex('o', -1) # => 2 + * s.byteindex('o', -2) # => 1 + * s.byteindex('o', -3) # => 1 + * s.byteindex('o', -4) # => nil * - * 'foo'.byteindex('o', -1) # => 2 - * 'foo'.byteindex('o', -2) # => 1 - * 'foo'.byteindex('o', -3) # => 1 - * 'foo'.byteindex('o', -4) # => nil + * Raises IndexError if the byte at +offset+ is not the first byte of a character: * - * If +offset+ does not land on character (codepoint) boundary, +IndexError+ is - * raised. + * s = "\uFFFF\uFFFF" # => "\uFFFF\uFFFF" + * s.size # => 2 # Two 3-byte characters. + * s.bytesize # => 6 # Six bytes. + * s.byteindex("\uFFFF") # => 0 + * s.byteindex("\uFFFF", 1) # Raises IndexError + * s.byteindex("\uFFFF", 2) # Raises IndexError + * s.byteindex("\uFFFF", 3) # => 3 + * s.byteindex("\uFFFF", 4) # Raises IndexError + * s.byteindex("\uFFFF", 5) # Raises IndexError + * s.byteindex("\uFFFF", 6) # => nil * - * Related: String#index, String#byterindex. + * Related: see {Querying}[rdoc-ref:String@Querying]. */ static VALUE @@ -4014,7 +4719,19 @@ rb_str_byteindex_m(int argc, VALUE *argv, VALUE str) return Qnil; } -#ifdef HAVE_MEMRCHR +#ifndef HAVE_MEMRCHR +static void* +memrchr(const char *search_str, int chr, long search_len) +{ + const char *ptr = search_str + search_len; + while (ptr > search_str) { + if ((unsigned char)*(--ptr) == chr) return (void *)ptr; + } + + return ((void *)0); +} +#endif + static long str_rindex(VALUE str, VALUE sub, const char *s, rb_encoding *enc) { @@ -4031,6 +4748,10 @@ str_rindex(VALUE str, VALUE sub, const char *s, rb_encoding *enc) c = *t & 0xff; searchlen = s - sbeg + 1; + if (memcmp(s, t, slen) == 0) { + return s - sbeg; + } + do { hit = memrchr(sbeg, c, searchlen); if (!hit) break; @@ -4046,29 +4767,6 @@ str_rindex(VALUE str, VALUE sub, const char *s, rb_encoding *enc) return -1; } -#else -static long -str_rindex(VALUE str, VALUE sub, const char *s, rb_encoding *enc) -{ - long slen; - char *sbeg, *e, *t; - - sbeg = RSTRING_PTR(str); - e = RSTRING_END(str); - t = RSTRING_PTR(sub); - slen = RSTRING_LEN(sub); - - while (s) { - if (memcmp(s, t, slen) == 0) { - return s - sbeg; - } - if (s <= sbeg) break; - s = rb_enc_prev_char(sbeg, s, e, enc); - } - - return -1; -} -#endif /* found index in byte */ static long @@ -4105,59 +4803,10 @@ rb_str_rindex(VALUE str, VALUE sub, long pos) /* * call-seq: - * rindex(substring, offset = self.length) -> integer or nil - * rindex(regexp, offset = self.length) -> integer or nil - * - * Returns the \Integer index of the _last_ occurrence of the given +substring+, - * or +nil+ if none found: - * - * 'foo'.rindex('f') # => 0 - * 'foo'.rindex('o') # => 2 - * 'foo'.rindex('oo') # => 1 - * 'foo'.rindex('ooo') # => nil - * - * Returns the \Integer index of the _last_ match for the given \Regexp +regexp+, - * or +nil+ if none found: - * - * 'foo'.rindex(/f/) # => 0 - * 'foo'.rindex(/o/) # => 2 - * 'foo'.rindex(/oo/) # => 1 - * 'foo'.rindex(/ooo/) # => nil - * - * The _last_ match means starting at the possible last position, not - * the last of longest matches. - * - * 'foo'.rindex(/o+/) # => 2 - * $~ #=> #<MatchData "o"> - * - * To get the last longest match, needs to combine with negative - * lookbehind. - * - * 'foo'.rindex(/(?<!o)o+/) # => 1 - * $~ #=> #<MatchData "oo"> - * - * Or String#index with negative lookforward. - * - * 'foo'.index(/o+(?!.*o)/) # => 1 - * $~ #=> #<MatchData "oo"> - * - * \Integer argument +offset+, if given and non-negative, specifies the maximum starting position in the - * string to _end_ the search: - * - * 'foo'.rindex('o', 0) # => nil - * 'foo'.rindex('o', 1) # => 1 - * 'foo'.rindex('o', 2) # => 2 - * 'foo'.rindex('o', 3) # => 2 - * - * If +offset+ is a negative \Integer, the maximum starting position in the - * string to _end_ the search is the sum of the string's length and +offset+: + * rindex(pattern, offset = self.length) -> integer or nil * - * 'foo'.rindex('o', -1) # => 2 - * 'foo'.rindex('o', -2) # => 1 - * 'foo'.rindex('o', -3) # => nil - * 'foo'.rindex('o', -4) # => nil + * :include:doc/string/rindex.rdoc * - * Related: String#index. */ static VALUE @@ -4235,65 +4884,90 @@ rb_str_byterindex(VALUE str, VALUE sub, long pos) return str_rindex(str, sub, s, enc); } - /* * call-seq: - * byterindex(substring, offset = self.bytesize) -> integer or nil - * byterindex(regexp, offset = self.bytesize) -> integer or nil + * byterindex(object, 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: + * Returns the 0-based integer index of a substring of +self+ + * that is the _last_ match for the given +object+ (a string or Regexp) and +offset+, + * or +nil+ if there is no such substring; + * the returned index is the count of _bytes_ (not characters). * - * 'foo'.byterindex('f') # => 0 - * 'foo'.byterindex('o') # => 2 - * 'foo'.byterindex('oo') # => 1 - * 'foo'.byterindex('ooo') # => nil + * When +object+ is a string, + * returns the index of the _last_ found substring equal to +object+: * - * Returns the \Integer byte-based index of the _last_ match for the given \Regexp +regexp+, - * or +nil+ if none found: + * s = 'foo' # => "foo" + * s.size # => 3 # Three 1-byte characters. + * s.bytesize # => 3 # Three bytes. + * s.byterindex('f') # => 0 + s.byterindex('o') # => 2 + s.byterindex('oo') # => 1 + s.byterindex('ooo') # => nil * - * 'foo'.byterindex(/f/) # => 0 - * 'foo'.byterindex(/o/) # => 2 - * 'foo'.byterindex(/oo/) # => 1 - * 'foo'.byterindex(/ooo/) # => nil + * When +object+ is a Regexp, + * returns the index of the last found substring matching +object+; + * updates {Regexp-related global variables}[rdoc-ref:Regexp@Global+Variables]: * - * The _last_ match means starting at the possible last position, not - * the last of longest matches. + * s = 'foo' + * s.byterindex(/f/) # => 0 + * $~ # => #<MatchData "f"> + * s.byterindex(/o/) # => 2 + * s.byterindex(/oo/) # => 1 + * s.byterindex(/ooo/) # => nil + * $~ # => nil * - * 'foo'.byterindex(/o+/) # => 2 - * $~ #=> #<MatchData "o"> + * The last match means starting at the possible last position, + * not the last of the longest matches: * - * To get the last longest match, needs to combine with negative - * lookbehind. + * s = 'foo' + * s.byterindex(/o+/) # => 2 + * $~ #=> #<MatchData "o"> * - * 'foo'.byterindex(/(?<!o)o+/) # => 1 - * $~ #=> #<MatchData "oo"> + * To get the last longest match, use a negative lookbehind: * - * Or String#byteindex with negative lookforward. + * s = 'foo' + * s.byterindex(/(?<!o)o+/) # => 1 + * $~ # => #<MatchData "oo"> * - * 'foo'.byteindex(/o+(?!.*o)/) # => 1 - * $~ #=> #<MatchData "oo"> + * Or use method #byteindex with negative lookahead: * - * \Integer argument +offset+, if given and non-negative, specifies the maximum starting byte-based position in the - * string to _end_ the search: + * s = 'foo' + * s.byteindex(/o+(?!.*o)/) # => 1 + * $~ #=> #<MatchData "oo"> * - * 'foo'.byterindex('o', 0) # => nil - * 'foo'.byterindex('o', 1) # => 1 - * 'foo'.byterindex('o', 2) # => 2 - * 'foo'.byterindex('o', 3) # => 2 + * \Integer argument +offset+, if given, specifies the 0-based index + * of the byte where searching is to end. * - * If +offset+ is a negative \Integer, the maximum starting position in the - * string to _end_ the search is the sum of the string's length and +offset+: + * When +offset+ is non-negative, + * searching ends at byte position +offset+: * - * 'foo'.byterindex('o', -1) # => 2 - * 'foo'.byterindex('o', -2) # => 1 - * 'foo'.byterindex('o', -3) # => nil - * 'foo'.byterindex('o', -4) # => nil + * s = 'foo' + * s.byterindex('o', 0) # => nil + * s.byterindex('o', 1) # => 1 + * s.byterindex('o', 2) # => 2 + * s.byterindex('o', 3) # => 2 + * + * When +offset+ is negative, counts backward from the end of +self+: + * + * s = 'foo' + * s.byterindex('o', -1) # => 2 + * s.byterindex('o', -2) # => 1 + * s.byterindex('o', -3) # => nil * - * If +offset+ does not land on character (codepoint) boundary, +IndexError+ is - * raised. + * Raises IndexError if the byte at +offset+ is not the first byte of a character: * - * Related: String#byteindex. + * s = "\uFFFF\uFFFF" # => "\uFFFF\uFFFF" + * s.size # => 2 # Two 3-byte characters. + * s.bytesize # => 6 # Six bytes. + * s.byterindex("\uFFFF") # => 3 + * s.byterindex("\uFFFF", 1) # Raises IndexError + * s.byterindex("\uFFFF", 2) # Raises IndexError + * s.byterindex("\uFFFF", 3) # => 3 + * s.byterindex("\uFFFF", 4) # Raises IndexError + * s.byterindex("\uFFFF", 5) # Raises IndexError + * s.byterindex("\uFFFF", 6) # => nil + * + * Related: see {Querying}[rdoc-ref:String@Querying]. */ static VALUE @@ -4337,30 +5011,36 @@ rb_str_byterindex_m(int argc, VALUE *argv, VALUE str) /* * call-seq: - * string =~ regexp -> integer or nil - * string =~ object -> integer or nil + * self =~ other -> integer or nil + * + * When +other+ is a Regexp: * - * Returns the \Integer index of the first substring that matches - * the given +regexp+, or +nil+ if no match found: + * - Returns the integer index (in characters) of the first match + * for +self+ and +other+, or +nil+ if none; + * - Updates {Regexp-related global variables}[rdoc-ref:Regexp@Global+Variables]. + * + * Examples: * * 'foo' =~ /f/ # => 0 + * $~ # => #<MatchData "f"> * 'foo' =~ /o/ # => 1 + * $~ # => #<MatchData "o"> * 'foo' =~ /x/ # => nil - * - * Note: also updates Regexp@Global+Variables. - * - * If the given +object+ is not a \Regexp, returns the value - * returned by <tt>object =~ self</tt>. + * $~ # => nil * * Note that <tt>string =~ regexp</tt> is different from <tt>regexp =~ string</tt> * (see Regexp#=~): * - * number= nil - * "no. 9" =~ /(?<number>\d+)/ - * number # => nil (not assigned) - * /(?<number>\d+)/ =~ "no. 9" - * number #=> "9" + * number = nil + * 'no. 9' =~ /(?<number>\d+)/ # => 4 + * number # => nil # Not assigned. + * /(?<number>\d+)/ =~ 'no. 9' # => 4 + * number # => "9" # Assigned. * + * When +other+ is not a Regexp, returns the value + * returned by <tt>other =~ self</tt>. + * + * Related: see {Querying}[rdoc-ref:String@Querying]. */ static VALUE @@ -4387,34 +5067,36 @@ static VALUE get_pat(VALUE); * match(pattern, offset = 0) -> matchdata or nil * match(pattern, offset = 0) {|matchdata| ... } -> object * - * Returns a \MatchData object (or +nil+) based on +self+ and the given +pattern+. + * Creates a MatchData object based on +self+ and the given arguments; + * updates {Regexp Global Variables}[rdoc-ref:Regexp@Global+Variables]. * - * Note: also updates Regexp@Global+Variables. + * - Computes +regexp+ by converting +pattern+ (if not already a Regexp). * - * - Computes +regexp+ by converting +pattern+ (if not already a \Regexp). * regexp = Regexp.new(pattern) - * - Computes +matchdata+, which will be either a \MatchData object or +nil+ - * (see Regexp#match): - * matchdata = <tt>regexp.match(self) * - * With no block given, returns the computed +matchdata+: + * - Computes +matchdata+, which will be either a MatchData object or +nil+ + * (see Regexp#match): * - * 'foo'.match('f') # => #<MatchData "f"> - * 'foo'.match('o') # => #<MatchData "o"> - * 'foo'.match('x') # => nil + * matchdata = regexp.match(self[offset..]) * - * If \Integer argument +offset+ is given, the search begins at index +offset+: + * With no block given, returns the computed +matchdata+ or +nil+: * + * 'foo'.match('f') # => #<MatchData "f"> + * 'foo'.match('o') # => #<MatchData "o"> + * 'foo'.match('x') # => nil * 'foo'.match('f', 1) # => nil * 'foo'.match('o', 1) # => #<MatchData "o"> * - * With a block given, calls the block with the computed +matchdata+ - * and returns the block's return value: + * With a block given and computed +matchdata+ non-nil, calls the block with +matchdata+; + * returns the block's return value: * * 'foo'.match(/o/) {|matchdata| matchdata } # => #<MatchData "o"> - * 'foo'.match(/x/) {|matchdata| matchdata } # => nil - * 'foo'.match(/f/, 1) {|matchdata| matchdata } # => nil * + * With a block given and +nil+ +matchdata+, does not call the block: + * + * 'foo'.match(/x/) {|matchdata| fail 'Cannot happen' } # => nil + * + * Related: see {Querying}[rdoc-ref:String@Querying]. */ static VALUE @@ -4436,24 +5118,23 @@ rb_str_match_m(int argc, VALUE *argv, VALUE str) * call-seq: * match?(pattern, offset = 0) -> true or false * - * Returns +true+ or +false+ based on whether a match is found for +self+ and +pattern+. + * Returns whether a match is found for +self+ and the given arguments; + * does not update {Regexp Global Variables}[rdoc-ref:Regexp@Global+Variables]. * - * Note: does not update Regexp@Global+Variables. + * Computes +regexp+ by converting +pattern+ (if not already a Regexp): * - * Computes +regexp+ by converting +pattern+ (if not already a \Regexp). * regexp = Regexp.new(pattern) * - * Returns +true+ if <tt>self+.match(regexp)</tt> returns a \MatchData object, + * Returns +true+ if <tt>self[offset..].match(regexp)</tt> returns a MatchData object, * +false+ otherwise: * * 'foo'.match?(/o/) # => true * 'foo'.match?('o') # => true * 'foo'.match?(/x/) # => false - * - * If \Integer argument +offset+ is given, the search begins at index +offset+: * 'foo'.match?('f', 1) # => false * 'foo'.match?('o', 1) # => true * + * Related: see {Querying}[rdoc-ref:String@Querying]. */ static VALUE @@ -4654,57 +5335,7 @@ static VALUE str_succ(VALUE str); * call-seq: * succ -> new_str * - * Returns the successor to +self+. The successor is calculated by - * incrementing characters. - * - * The first character to be incremented is the rightmost alphanumeric: - * or, if no alphanumerics, the rightmost character: - * - * 'THX1138'.succ # => "THX1139" - * '<<koala>>'.succ # => "<<koalb>>" - * '***'.succ # => '**+' - * - * The successor to a digit is another digit, "carrying" to the next-left - * character for a "rollover" from 9 to 0, and prepending another digit - * if necessary: - * - * '00'.succ # => "01" - * '09'.succ # => "10" - * '99'.succ # => "100" - * - * The successor to a letter is another letter of the same case, - * carrying to the next-left character for a rollover, - * and prepending another same-case letter if necessary: - * - * 'aa'.succ # => "ab" - * 'az'.succ # => "ba" - * 'zz'.succ # => "aaa" - * 'AA'.succ # => "AB" - * 'AZ'.succ # => "BA" - * 'ZZ'.succ # => "AAA" - * - * The successor to a non-alphanumeric character is the next character - * in the underlying character set's collating sequence, - * carrying to the next-left character for a rollover, - * and prepending another character if necessary: - * - * s = 0.chr * 3 - * s # => "\x00\x00\x00" - * s.succ # => "\x00\x00\x01" - * s = 255.chr * 3 - * s # => "\xFF\xFF\xFF" - * s.succ # => "\x01\x00\x00\x00" - * - * Carrying can occur between and among mixtures of alphanumeric characters: - * - * s = 'zz99zz99' - * s.succ # => "aaa00aa00" - * s = '99zz99zz' - * s.succ # => "100aa00aa" - * - * The successor to an empty \String is a new empty \String: - * - * ''.succ # => "" + * :include: doc/string/succ.rdoc * */ @@ -4809,7 +5440,9 @@ str_succ(VALUE str) * call-seq: * succ! -> self * - * Equivalent to String#succ, but modifies +self+ in place; returns +self+. + * Like String#succ, but modifies +self+ in place; returns +self+. + * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE @@ -4842,33 +5475,7 @@ str_upto_i(VALUE str, VALUE arg) * upto(other_string, exclusive = false) {|string| ... } -> self * upto(other_string, exclusive = false) -> new_enumerator * - * With a block given, calls the block with each \String value - * returned by successive calls to String#succ; - * the first value is +self+, the next is <tt>self.succ</tt>, and so on; - * the sequence terminates when value +other_string+ is reached; - * returns +self+: - * - * 'a8'.upto('b6') {|s| print s, ' ' } # => "a8" - * Output: - * - * a8 a9 b0 b1 b2 b3 b4 b5 b6 - * - * If argument +exclusive+ is given as a truthy object, the last value is omitted: - * - * 'a8'.upto('b6', true) {|s| print s, ' ' } # => "a8" - * - * Output: - * - * a8 a9 b0 b1 b2 b3 b4 b5 - * - * If +other_string+ would not be reached, does not call the block: - * - * '25'.upto('5') {|s| fail s } - * 'aa'.upto('a') {|s| fail s } - * - * With no block given, returns a new \Enumerator: - * - * 'a8'.upto('b6') # => #<Enumerator: "a8":upto("b6")> + * :include: doc/string/upto.rdoc * */ @@ -4901,7 +5508,9 @@ rb_str_upto_each(VALUE beg, VALUE end, int excl, int (*each)(VALUE, VALUE), VALU if (c > e || (excl && c == e)) return beg; for (;;) { - if ((*each)(rb_enc_str_new(&c, 1, enc), arg)) break; + VALUE str = rb_enc_str_new(&c, 1, enc); + ENC_CODERANGE_SET(str, RUBY_ENC_CODERANGE_7BIT); + if ((*each)(str, arg)) break; if (!excl && c == e) break; c++; if (excl && c == e) break; @@ -5107,15 +5716,13 @@ rb_str_aref(VALUE str, VALUE indx) /* * call-seq: - * string[index] -> new_string or nil - * string[start, length] -> new_string or nil - * string[range] -> new_string or nil - * string[regexp, capture = 0] -> new_string or nil - * string[substring] -> new_string or nil - * - * Returns the substring of +self+ specified by the arguments. - * See examples at {String Slices}[rdoc-ref:String@String+Slices]. + * self[offset] -> new_string or nil + * self[offset, size] -> new_string or nil + * self[range] -> new_string or nil + * self[regexp, capture = 0] -> new_string or nil + * self[substring] -> new_string or nil * + * :include: doc/string/aref.rdoc * */ @@ -5127,9 +5734,7 @@ rb_str_aref_m(int argc, VALUE *argv, VALUE str) return rb_str_subpat(str, argv[0], argv[1]); } else { - long beg = NUM2LONG(argv[0]); - long len = NUM2LONG(argv[1]); - return rb_str_substr(str, beg, len); + return rb_str_substr_two_fixnums(str, argv[0], argv[1], TRUE); } } rb_check_arity(argc, 1, 2); @@ -5239,8 +5844,9 @@ rb_str_update(VALUE str, long beg, long len, VALUE val) if (beg < 0) { beg += slen; } - assert(beg >= 0); - assert(beg <= slen); + RUBY_ASSERT(beg >= 0); + RUBY_ASSERT(beg <= slen); + if (len > slen - beg) { len = slen - beg; } @@ -5331,28 +5937,13 @@ rb_str_aset(VALUE str, VALUE indx, VALUE val) /* * call-seq: - * string[index] = new_string - * string[start, length] = new_string - * string[range] = new_string - * string[regexp, capture = 0] = new_string - * string[substring] = new_string - * - * Replaces all, some, or none of the contents of +self+; returns +new_string+. - * See {String Slices}[rdoc-ref:String@String+Slices]. + * self[index] = other_string -> new_string + * self[start, length] = other_string -> new_string + * self[range] = other_string -> new_string + * self[regexp, capture = 0] = other_string -> new_string + * self[substring] = other_string -> new_string * - * A few examples: - * - * s = 'foo' - * s[2] = 'rtune' # => "rtune" - * s # => "fortune" - * s[1, 5] = 'init' # => "init" - * s # => "finite" - * s[3..4] = 'al' # => "al" - * s # => "finale" - * s[/e$/] = 'ly' # => "ly" - * s # => "finally" - * s['lly'] = 'ncial' # => "ncial" - * s # => "financial" + * :include: doc/string/aset.rdoc * */ @@ -5374,19 +5965,9 @@ rb_str_aset_m(int argc, VALUE *argv, VALUE str) /* * call-seq: - * insert(index, other_string) -> self - * - * Inserts the given +other_string+ into +self+; returns +self+. + * insert(offset, other_string) -> self * - * If the \Integer +index+ is positive, inserts +other_string+ at offset +index+: - * - * 'foo'.insert(1, 'bar') # => "fbaroo" - * - * If the \Integer +index+ is negative, counts backward from the end of +self+ - * and inserts +other_string+ at offset <tt>index+1</tt> - * (that is, _after_ <tt>self[index]</tt>): - * - * 'foo'.insert(-2, 'bar') # => "fobaro" + * :include: doc/string/insert.rdoc * */ @@ -5414,18 +5995,20 @@ rb_str_insert(VALUE str, VALUE idx, VALUE str2) * slice!(regexp, capture = 0) -> new_string or nil * slice!(substring) -> new_string or nil * - * Removes and returns the substring of +self+ specified by the arguments. - * See {String Slices}[rdoc-ref:String@String+Slices]. + * Like String#[] (and its alias String#slice), except that: + * + * - Performs substitutions in +self+ (not in a copy of +self+). + * - Returns the removed substring if any modifications were made, +nil+ otherwise. * * A few examples: * - * string = "This is a string" - * string.slice!(2) #=> "i" - * string.slice!(3..6) #=> " is " - * string.slice!(/s.*t/) #=> "sa st" - * string.slice!("r") #=> "r" - * string #=> "Thing" + * s = 'hello' + * s.slice!('e') # => "e" + * s # => "hllo" + * s.slice!('e') # => nil + * s # => "hllo" * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE @@ -5564,14 +6147,17 @@ get_pat_quoted(VALUE pat, int check) } static long -rb_pat_search(VALUE pat, VALUE str, long pos, int set_backref_str) +rb_pat_search0(VALUE pat, VALUE str, long pos, int set_backref_str, VALUE *match) { if (BUILTIN_TYPE(pat) == T_STRING) { pos = rb_str_byteindex(str, pat, pos); if (set_backref_str) { if (pos >= 0) { str = rb_str_new_frozen_String(str); - rb_backref_set_string(str, pos, RSTRING_LEN(pat)); + VALUE match_data = rb_backref_set_string(str, pos, RSTRING_LEN(pat)); + if (match) { + *match = match_data; + } } else { rb_backref_set(Qnil); @@ -5580,23 +6166,28 @@ rb_pat_search(VALUE pat, VALUE str, long pos, int set_backref_str) return pos; } else { - return rb_reg_search0(pat, str, pos, 0, set_backref_str); + return rb_reg_search0(pat, str, pos, 0, set_backref_str, match); } } +static long +rb_pat_search(VALUE pat, VALUE str, long pos, int set_backref_str) +{ + return rb_pat_search0(pat, str, pos, set_backref_str, NULL); +} + /* * call-seq: * sub!(pattern, replacement) -> self or nil * sub!(pattern) {|match| ... } -> self or nil * - * Returns +self+ with only the first occurrence - * (not all occurrences) of the given +pattern+ replaced. - * - * See {Substitution Methods}[rdoc-ref:String@Substitution+Methods]. + * Like String#sub, except that: * - * Related: String#sub, String#gsub, String#gsub!. + * - Changes are made to +self+, not to copy of +self+. + * - Returns +self+ if any changes are made, +nil+ otherwise. * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE @@ -5670,8 +6261,8 @@ rb_str_sub_bang(int argc, VALUE *argv, VALUE str) if (coderange_scan(p, beg0, str_enc) != ENC_CODERANGE_7BIT || coderange_scan(p+end0, len-end0, str_enc) != ENC_CODERANGE_7BIT) { rb_raise(rb_eEncCompatError, "incompatible character encodings: %s and %s", - rb_enc_name(str_enc), - rb_enc_name(STR_ENC_GET(repl))); + rb_enc_inspect_name(str_enc), + rb_enc_inspect_name(STR_ENC_GET(repl))); } enc = STR_ENC_GET(repl); } @@ -5702,6 +6293,8 @@ rb_str_sub_bang(int argc, VALUE *argv, VALUE str) TERM_FILL(&RSTRING_PTR(str)[len], TERM_LEN(str)); ENC_CODERANGE_SET(str, cr); + RB_GC_GUARD(match); + return str; } return Qnil; @@ -5713,13 +6306,7 @@ rb_str_sub_bang(int argc, VALUE *argv, VALUE str) * sub(pattern, replacement) -> new_string * sub(pattern) {|match| ... } -> new_string * - * Returns a copy of +self+ with only the first occurrence - * (not all occurrences) of the given +pattern+ replaced. - * - * See {Substitution Methods}[rdoc-ref:String@Substitution+Methods]. - * - * Related: String#sub!, String#gsub, String#gsub!. - * + * :include: doc/string/sub.rdoc */ static VALUE @@ -5733,13 +6320,12 @@ rb_str_sub(int argc, VALUE *argv, VALUE str) static VALUE str_gsub(int argc, VALUE *argv, VALUE str, int bang) { - VALUE pat, val = Qnil, repl, match, match0 = Qnil, dest, hash = Qnil; - struct re_registers *regs; + VALUE pat, val = Qnil, repl, match0 = Qnil, dest, hash = Qnil, match = Qnil; long beg, beg0, end0; long offset, blen, slen, len, last; - enum {STR, ITER, MAP} mode = STR; + enum {STR, ITER, FAST_MAP, MAP} mode = STR; char *sp, *cp; - int need_backref = -1; + int need_backref_str = -1; rb_encoding *str_enc; switch (argc) { @@ -5753,6 +6339,9 @@ str_gsub(int argc, VALUE *argv, VALUE str, int bang) if (NIL_P(hash)) { StringValue(repl); } + else if (rb_hash_default_unredefined(hash) && !FL_TEST_RAW(hash, RHASH_PROC_DEFAULT)) { + mode = FAST_MAP; + } else { mode = MAP; } @@ -5762,7 +6351,8 @@ str_gsub(int argc, VALUE *argv, VALUE str, int bang) } pat = get_pat_quoted(argv[0], 1); - beg = rb_pat_search(pat, str, 0, need_backref); + beg = rb_pat_search0(pat, str, 0, need_backref_str, &match); + if (beg < 0) { if (bang) return Qnil; /* no match, no substitution */ return str_duplicate(rb_cString, str); @@ -5779,8 +6369,7 @@ str_gsub(int argc, VALUE *argv, VALUE str, int bang) ENC_CODERANGE_SET(dest, rb_enc_asciicompat(str_enc) ? ENC_CODERANGE_7BIT : ENC_CODERANGE_VALID); do { - match = rb_backref_get(); - regs = RMATCH_REGS(match); + struct re_registers *regs = RMATCH_REGS(match); if (RB_TYPE_P(pat, T_STRING)) { beg0 = beg; end0 = beg0 + RSTRING_LEN(pat); @@ -5792,12 +6381,23 @@ str_gsub(int argc, VALUE *argv, VALUE str, int bang) if (mode == ITER) match0 = rb_reg_nth_match(0, match); } - if (mode) { + if (mode != STR) { if (mode == ITER) { val = rb_obj_as_string(rb_yield(match0)); } else { - val = rb_hash_aref(hash, rb_str_subseq(str, beg0, end0 - beg0)); + struct RString fake_str = {RBASIC_INIT}; + VALUE key; + if (mode == FAST_MAP) { + // It is safe to use a fake_str here because we established that it won't escape, + // as it's only used for `rb_hash_aref` and we checked the hash doesn't have a + // default proc. + key = setup_fake_str(&fake_str, sp + beg0, end0 - beg0, ENCODING_GET_INLINED(str)); + } + else { + key = rb_str_subseq(str, beg0, end0 - beg0); + } + val = rb_hash_aref(hash, key); val = rb_obj_as_string(val); } str_mod_check(str, sp, slen); @@ -5805,10 +6405,10 @@ str_gsub(int argc, VALUE *argv, VALUE str, int bang) rb_raise(rb_eRuntimeError, "block should not cheat"); } } - else if (need_backref) { + else if (need_backref_str) { val = rb_reg_regsub(repl, str, regs, RB_TYPE_P(pat, T_STRING) ? Qnil : pat); - if (need_backref < 0) { - need_backref = val != repl; + if (need_backref_str < 0) { + need_backref_str = val != repl; } } else { @@ -5836,12 +6436,20 @@ str_gsub(int argc, VALUE *argv, VALUE str, int bang) } cp = RSTRING_PTR(str) + offset; if (offset > RSTRING_LEN(str)) break; - beg = rb_pat_search(pat, str, offset, need_backref); + + // In FAST_MAP and STR mode the backref can't escape so we can re-use the MatchData safely. + if (mode != FAST_MAP && mode != STR) { + match = Qnil; + } + beg = rb_pat_search0(pat, str, offset, need_backref_str, &match); + + RB_GC_GUARD(match); } while (beg >= 0); + if (RSTRING_LEN(str) > offset) { rb_enc_str_buf_cat(dest, cp, RSTRING_LEN(str) - offset, str_enc); } - rb_pat_search(pat, str, last, 1); + rb_pat_search0(pat, str, last, 1, &match); if (bang) { str_shared_replace(str, dest); } @@ -5859,15 +6467,12 @@ str_gsub(int argc, VALUE *argv, VALUE str, int bang) * gsub!(pattern) {|match| ... } -> self or nil * gsub!(pattern) -> an_enumerator * - * Performs the specified substring replacement(s) on +self+; - * returns +self+ if any replacement occurred, +nil+ otherwise. + * Like String#gsub, except that: * - * See {Substitution Methods}[rdoc-ref:String@Substitution+Methods]. - * - * Returns an Enumerator if no +replacement+ and no block given. - * - * Related: String#sub, String#gsub, String#sub!. + * - Performs substitutions in +self+ (not in a copy of +self+). + * - Returns +self+ if any characters are removed, +nil+ otherwise. * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE @@ -5884,14 +6489,41 @@ rb_str_gsub_bang(int argc, VALUE *argv, VALUE str) * gsub(pattern) {|match| ... } -> new_string * gsub(pattern) -> enumerator * - * Returns a copy of +self+ with all occurrences of the given +pattern+ replaced. + * Returns a copy of +self+ with zero or more substrings replaced. + * + * Argument +pattern+ may be a string or a Regexp; + * argument +replacement+ may be a string or a Hash. + * Varying types for the argument values makes this method very versatile. + * + * Below are some simple examples; + * for many more examples, see {Substitution Methods}[rdoc-ref:String@Substitution+Methods]. + * + * With arguments +pattern+ and string +replacement+ given, + * replaces each matching substring with the given +replacement+ string: + * + * s = 'abracadabra' + * s.gsub('ab', 'AB') # => "ABracadABra" + * s.gsub(/[a-c]/, 'X') # => "XXrXXXdXXrX" + * + * With arguments +pattern+ and hash +replacement+ given, + * replaces each matching substring with a value from the given +replacement+ hash, + * or removes it: + * + * h = {'a' => 'A', 'b' => 'B', 'c' => 'C'} + * s.gsub(/[a-c]/, h) # => "ABrACAdABrA" # 'a', 'b', 'c' replaced. + * s.gsub(/[a-d]/, h) # => "ABrACAABrA" # 'd' removed. * - * See {Substitution Methods}[rdoc-ref:String@Substitution+Methods]. + * With argument +pattern+ and a block given, + * calls the block with each matching substring; + * replaces that substring with the block's return value: * - * Returns an Enumerator if no +replacement+ and no block given. + * s.gsub(/[a-d]/) {|substring| substring.upcase } + * # => "ABrACADABrA" * - * Related: String#sub, String#sub!, String#gsub!. + * With argument +pattern+ and no block given, + * returns a new Enumerator. * + * Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. */ static VALUE @@ -5905,11 +6537,13 @@ rb_str_gsub(int argc, VALUE *argv, VALUE str) * call-seq: * replace(other_string) -> self * - * Replaces the contents of +self+ with the contents of +other_string+: + * Replaces the contents of +self+ with the contents of +other_string+; + * returns +self+: * * s = 'foo' # => "foo" * s.replace('bar') # => "bar" * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ VALUE @@ -5929,9 +6563,11 @@ rb_str_replace(VALUE str, VALUE str2) * * Removes the contents of +self+: * - * s = 'foo' # => "foo" - * s.clear # => "" + * s = 'foo' + * s.clear # => "" + * s # => "" * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE @@ -5952,10 +6588,7 @@ rb_str_clear(VALUE str) * call-seq: * chr -> string * - * Returns a string containing the first character of +self+: - * - * s = 'foo' # => "foo" - * s.chr # => "f" + * :include: doc/string/chr.rdoc * */ @@ -5969,14 +6602,8 @@ rb_str_chr(VALUE str) * call-seq: * getbyte(index) -> integer or nil * - * Returns the byte at zero-based +index+ as an integer, or +nil+ if +index+ is out of range: - * - * s = 'abcde' # => "abcde" - * s.getbyte(0) # => 97 - * s.getbyte(-1) # => 101 - * s.getbyte(5) # => nil + * :include: doc/string/getbyte.rdoc * - * Related: String#setbyte. */ VALUE rb_str_getbyte(VALUE str, VALUE index) @@ -5995,15 +6622,16 @@ rb_str_getbyte(VALUE str, VALUE index) * call-seq: * setbyte(index, integer) -> integer * - * Sets the byte at zero-based +index+ to +integer+; returns +integer+: + * Sets the byte at zero-based offset +index+ to the value of the given +integer+; + * returns +integer+: * - * s = 'abcde' # => "abcde" - * s.setbyte(0, 98) # => 98 - * s # => "bbcde" + * s = 'xyzzy' + * s.setbyte(2, 129) # => 129 + * s # => "xy\x81zy" * - * Related: String#getbyte. + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ -static VALUE +VALUE rb_str_setbyte(VALUE str, VALUE index, VALUE value) { long pos = NUM2LONG(index); @@ -6099,6 +6727,12 @@ str_byte_substr(VALUE str, long beg, long len, int empty) return str2; } +VALUE +rb_str_byte_substr(VALUE str, VALUE beg, VALUE len) +{ + return str_byte_substr(str, NUM2LONG(beg), NUM2LONG(len), TRUE); +} + static VALUE str_byte_aref(VALUE str, VALUE indx) { @@ -6126,45 +6760,10 @@ str_byte_aref(VALUE str, VALUE indx) /* * call-seq: - * byteslice(index, length = 1) -> string or nil - * byteslice(range) -> string or nil - * - * Returns a substring of +self+, or +nil+ if the substring cannot be constructed. - * - * With integer arguments +index+ and +length+ given, - * returns the substring beginning at the given +index+ - * of the given +length+ (if possible), - * or +nil+ if +length+ is negative or +index+ falls outside of +self+: - * - * s = '0123456789' # => "0123456789" - * s.byteslice(2) # => "2" - * s.byteslice(200) # => nil - * s.byteslice(4, 3) # => "456" - * s.byteslice(4, 30) # => "456789" - * s.byteslice(4, -1) # => nil - * s.byteslice(40, 2) # => nil - * - * In either case above, counts backwards from the end of +self+ - * if +index+ is negative: - * - * s = '0123456789' # => "0123456789" - * s.byteslice(-4) # => "6" - * s.byteslice(-4, 3) # => "678" - * - * With Range argument +range+ given, returns - * <tt>byteslice(range.begin, range.size)</tt>: - * - * s = '0123456789' # => "0123456789" - * s.byteslice(4..6) # => "456" - * s.byteslice(-6..-4) # => "456" - * s.byteslice(5..2) # => "" # range.size is zero. - * s.byteslice(40..42) # => nil - * - * In all cases, a returned string has the same encoding as +self+: - * - * s.encoding # => #<Encoding:UTF-8> - * s.byteslice(4).encoding # => #<Encoding:UTF-8> + * byteslice(offset, length = 1) -> string or nil + * byteslice(range) -> string or nil * + * :include: doc/string/byteslice.rdoc */ static VALUE @@ -6191,8 +6790,9 @@ str_check_beg_len(VALUE str, long *beg, long *len) if (*beg < 0) { *beg += slen; } - assert(*beg >= 0); - assert(*beg <= slen); + RUBY_ASSERT(*beg >= 0); + RUBY_ASSERT(*beg <= slen); + if (*len > slen - *beg) { *len = slen - *beg; } @@ -6203,23 +6803,12 @@ str_check_beg_len(VALUE str, long *beg, long *len) /* * 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 + * bytesplice(offset, length, str) -> self + * bytesplice(offset, length, str, str_offset, str_length) -> self + * bytesplice(range, str) -> self + * bytesplice(range, str, str_range) -> self * - * 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. + * :include: doc/string/bytesplice.rdoc */ static VALUE @@ -6227,7 +6816,6 @@ rb_str_bytesplice(int argc, VALUE *argv, VALUE str) { long beg, len, vbeg, vlen; VALUE val; - rb_encoding *enc; int cr; rb_check_arity(argc, 2, 5); @@ -6272,10 +6860,13 @@ rb_str_bytesplice(int argc, VALUE *argv, VALUE str) } str_check_beg_len(str, &beg, &len); str_check_beg_len(val, &vbeg, &vlen); - enc = rb_enc_check(str, val); str_modify_keep_cr(str); + + if (RB_UNLIKELY(ENCODING_GET_INLINED(str) != ENCODING_GET_INLINED(val))) { + rb_enc_associate(str, rb_enc_check(str, val)); + } + rb_str_update_1(str, beg, len, val, vbeg, vlen); - rb_enc_associate(str, enc); cr = ENC_CODERANGE_AND(ENC_CODERANGE(str), ENC_CODERANGE(val)); if (cr != ENC_CODERANGE_BROKEN) ENC_CODERANGE_SET(str, cr); @@ -6284,12 +6875,16 @@ rb_str_bytesplice(int argc, VALUE *argv, VALUE str) /* * call-seq: - * reverse -> string + * reverse -> new_string * * Returns a new string with the characters from +self+ in reverse order. * - * 'stressed'.reverse # => "desserts" + * 'drawer'.reverse # => "reward" + * 'reviled'.reverse # => "deliver" + * 'stressed'.reverse # => "desserts" + * 'semordnilaps'.reverse # => "spalindromes" * + * Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. */ static VALUE @@ -6349,10 +6944,12 @@ rb_str_reverse(VALUE str) * * Returns +self+ with its characters reversed: * - * s = 'stressed' - * s.reverse! # => "desserts" - * s # => "desserts" + * 'drawer'.reverse! # => "reward" + * 'reviled'.reverse! # => "deliver" + * 'stressed'.reverse! # => "desserts" + * 'semordnilaps'.reverse! # => "spalindromes" * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE @@ -6384,15 +6981,19 @@ rb_str_reverse_bang(VALUE str) /* * call-seq: - * include? other_string -> true or false + * include?(other_string) -> true or false * - * Returns +true+ if +self+ contains +other_string+, +false+ otherwise: + * Returns whether +self+ contains +other_string+: * - * s = 'foo' - * s.include?('f') # => true - * s.include?('fo') # => true - * s.include?('food') # => false + * s = 'bar' + * s.include?('ba') # => true + * s.include?('ar') # => true + * s.include?('bar') # => true + * s.include?('a') # => true + * s.include?('') # => true + * s.include?('foo') # => false * + * Related: see {Querying}[rdoc-ref:String@Querying]. */ VALUE @@ -6412,12 +7013,13 @@ rb_str_include(VALUE str, VALUE arg) * to_i(base = 10) -> integer * * Returns the result of interpreting leading characters in +self+ - * as an integer in the given +base+ (which must be in (0, 2..36)): + * as an integer in the given +base+; + * +base+ must be either +0+ or in range <tt>(2..36)</tt>: * * '123456'.to_i # => 123456 * '123def'.to_i(16) # => 1195503 * - * With +base+ zero, string +object+ may contain leading characters + * With +base+ zero given, string +object+ may contain leading characters * to specify the actual base: * * '123def'.to_i(0) # => 123 @@ -6437,6 +7039,7 @@ rb_str_include(VALUE str, VALUE arg) * 'abcdef'.to_i # => 0 * '2'.to_i(2) # => 0 * + * Related: see {Converting to Non-String}[rdoc-ref:String@Converting+to+Non--5CString]. */ static VALUE @@ -6458,9 +7061,9 @@ rb_str_to_i(int argc, VALUE *argv, VALUE str) * Returns the result of interpreting leading characters in +self+ as a Float: * * '3.14159'.to_f # => 3.14159 - '1.234e-2'.to_f # => 0.01234 + * '1.234e-2'.to_f # => 0.01234 * - * Characters past a leading valid number (in the given +base+) are ignored: + * Characters past a leading valid number are ignored: * * '3.14 (pi to two places)'.to_f # => 3.14 * @@ -6468,6 +7071,7 @@ rb_str_to_i(int argc, VALUE *argv, VALUE str) * * 'abcdef'.to_f # => 0.0 * + * See {Converting to Non-String}[rdoc-ref:String@Converting+to+Non--5CString]. */ static VALUE @@ -6479,10 +7083,12 @@ rb_str_to_f(VALUE str) /* * call-seq: - * to_s -> self or string + * to_s -> self or new_string * - * Returns +self+ if +self+ is a \String, - * or +self+ converted to a \String if +self+ is a subclass of \String. + * Returns +self+ if +self+ is a +String+, + * or +self+ converted to a +String+ if +self+ is a subclass of +String+. + * + * Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. */ static VALUE @@ -6615,12 +7221,7 @@ rb_str_escape(VALUE str) * call-seq: * inspect -> string * - * Returns a printable version of +self+, enclosed in double-quotes, - * and with special characters escaped: - * - * s = "foo\tbar\tbaz\n" - * s.inspect - * # => "\"foo\\tbar\\tbaz\\n\"" + * :include: doc/string/inspect.rdoc * */ @@ -6725,16 +7326,9 @@ rb_str_inspect(VALUE str) /* * call-seq: - * dump -> string + * dump -> new_string * - * Returns a printable version of +self+, enclosed in double-quotes, - * with special characters escaped, and with non-printing characters - * replaced by hexadecimal notation: - * - * "hello \n ''".dump # => "\"hello \\n ''\"" - * "\f\x00\xff\\\"".dump # => "\"\\f\\x00\\xFF\\\\\\\"\"" - * - * Related: String#undump (inverse of String#dump). + * :include: doc/string/dump.rdoc * */ @@ -6994,10 +7588,6 @@ undump_after_backslash(VALUE undumped, const char **ss, const char *s_end, rb_en } break; case 'x': - if (*utf8) { - rb_raise(rb_eRuntimeError, "hex escape and Unicode escape are mixed"); - } - *binary = true; if (++s >= s_end) { rb_raise(rb_eRuntimeError, "invalid hex escape"); } @@ -7005,6 +7595,12 @@ undump_after_backslash(VALUE undumped, const char **ss, const char *s_end, rb_en if (hexlen != 2) { rb_raise(rb_eRuntimeError, "invalid hex escape"); } + if (!ISASCII(*buf)) { + if (*utf8) { + rb_raise(rb_eRuntimeError, "hex escape and Unicode escape are mixed"); + } + *binary = true; + } rb_str_cat(undumped, (char *)buf, 1); s += hexlen; break; @@ -7020,17 +7616,11 @@ static VALUE rb_str_is_ascii_only_p(VALUE str); /* * call-seq: - * undump -> string - * - * Returns an unescaped version of +self+: - * - * s_orig = "\f\x00\xff\\\"" # => "\f\u0000\xFF\\\"" - * s_dumped = s_orig.dump # => "\"\\f\\x00\\xFF\\\\\\\"\"" - * s_undumped = s_dumped.undump # => "\f\u0000\xFF\\\"" - * s_undumped == s_orig # => true + * undump -> new_string * - * Related: String#dump (inverse of String#undump). + * Inverse of String#dump; returns a copy of +self+ with changes of the kinds made by String#dump "undone." * + * Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. */ static VALUE @@ -7117,6 +7707,8 @@ str_undump(VALUE str) } } + RB_GC_GUARD(str); + return undumped; invalid_format: rb_raise(rb_eRuntimeError, "invalid dumped string; not wrapped with '\"' nor '\"...\".force_encoding(\"...\")' form"); @@ -7353,21 +7945,14 @@ upcase_single(VALUE str) /* * call-seq: - * upcase!(*options) -> self or nil - * - * Upcases the characters in +self+; - * returns +self+ if any changes were made, +nil+ otherwise: + * upcase!(mapping) -> self or nil * - * s = 'Hello World!' # => "Hello World!" - * s.upcase! # => "HELLO WORLD!" - * s # => "HELLO WORLD!" - * s.upcase! # => nil + * Like String#upcase, except that: * - * The casing may be affected by the given +options+; - * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. - * - * Related: String#upcase, String#downcase, String#downcase!. + * - Changes character casings in +self+ (not in a copy of +self+). + * - Returns +self+ if any changes are made, +nil+ otherwise. * + * Related: See {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE @@ -7395,18 +7980,9 @@ rb_str_upcase_bang(int argc, VALUE *argv, VALUE str) /* * call-seq: - * upcase(*options) -> string - * - * Returns a string containing the upcased characters in +self+: - * - * s = 'Hello World!' # => "Hello World!" - * s.upcase # => "HELLO WORLD!" - * - * The casing may be affected by the given +options+; - * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. - * - * Related: String#upcase!, String#downcase, String#downcase!. + * upcase(mapping = :ascii) -> new_string * + * :include: doc/string/upcase.rdoc */ static VALUE @@ -7455,21 +8031,14 @@ downcase_single(VALUE str) /* * call-seq: - * downcase!(*options) -> self or nil - * - * Downcases the characters in +self+; - * returns +self+ if any changes were made, +nil+ otherwise: + * downcase!(mapping) -> self or nil * - * s = 'Hello World!' # => "Hello World!" - * s.downcase! # => "hello world!" - * s # => "hello world!" - * s.downcase! # => nil + * Like String#downcase, except that: * - * The casing may be affected by the given +options+; - * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. - * - * Related: String#downcase, String#upcase, String#upcase!. + * - Changes character casings in +self+ (not in a copy of +self+). + * - Returns +self+ if any changes are made, +nil+ otherwise. * + * Related: See {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE @@ -7497,17 +8066,9 @@ rb_str_downcase_bang(int argc, VALUE *argv, VALUE str) /* * call-seq: - * downcase(*options) -> string - * - * Returns a string containing the downcased characters in +self+: - * - * s = 'Hello World!' # => "Hello World!" - * s.downcase # => "hello world!" - * - * The casing may be affected by the given +options+; - * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. + * downcase(mapping = :ascii) -> new_string * - * Related: String#downcase!, String#upcase, String#upcase!. + * :include: doc/string/downcase.rdoc * */ @@ -7539,22 +8100,14 @@ rb_str_downcase(int argc, VALUE *argv, VALUE str) /* * call-seq: - * capitalize!(*options) -> self or nil + * capitalize!(mapping = :ascii) -> self or nil * - * Upcases the first character in +self+; - * downcases the remaining characters; - * returns +self+ if any changes were made, +nil+ otherwise: + * Like String#capitalize, except that: * - * s = 'hello World!' # => "hello World!" - * s.capitalize! # => "Hello world!" - * s # => "Hello world!" - * s.capitalize! # => nil - * - * The casing may be affected by the given +options+; - * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. - * - * Related: String#capitalize. + * - Changes character casings in +self+ (not in a copy of +self+). + * - Returns +self+ if any changes are made, +nil+ otherwise. * + * Related: See {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE @@ -7579,19 +8132,9 @@ rb_str_capitalize_bang(int argc, VALUE *argv, VALUE str) /* * call-seq: - * capitalize(*options) -> string - * - * Returns a string containing the characters in +self+; - * the first character is upcased; - * the remaining characters are downcased: - * - * s = 'hello World!' # => "hello World!" - * s.capitalize # => "Hello world!" + * capitalize(mapping = :ascii) -> new_string * - * The casing may be affected by the given +options+; - * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. - * - * Related: String#capitalize!. + * :include: doc/string/capitalize.rdoc * */ @@ -7618,22 +8161,14 @@ rb_str_capitalize(int argc, VALUE *argv, VALUE str) /* * call-seq: - * swapcase!(*options) -> self or nil - * - * Upcases each lowercase character in +self+; - * downcases uppercase character; - * returns +self+ if any changes were made, +nil+ otherwise: + * swapcase!(mapping) -> self or nil * - * s = 'Hello World!' # => "Hello World!" - * s.swapcase! # => "hELLO wORLD!" - * s # => "hELLO wORLD!" - * ''.swapcase! # => nil + * Like String#swapcase, except that: * - * The casing may be affected by the given +options+; - * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. - * - * Related: String#swapcase. + * - Changes are made to +self+, not to copy of +self+. + * - Returns +self+ if any changes are made, +nil+ otherwise. * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE @@ -7657,19 +8192,9 @@ rb_str_swapcase_bang(int argc, VALUE *argv, VALUE str) /* * call-seq: - * swapcase(*options) -> string - * - * Returns a string containing the characters in +self+, with cases reversed; - * each uppercase character is downcased; - * each lowercase character is upcased: + * swapcase(mapping = :ascii) -> new_string * - * s = 'Hello World!' # => "Hello World!" - * s.swapcase # => "hELLO wORLD!" - * - * The casing may be affected by the given +options+; - * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. - * - * Related: String#swapcase!. + * :include: doc/string/swapcase.rdoc * */ @@ -7864,7 +8389,14 @@ tr_trans(VALUE str, VALUE src, VALUE repl, int sflag) while (s < send) { int may_modify = 0; - c0 = c = rb_enc_codepoint_len((char *)s, (char *)send, &clen, e1); + int r = rb_enc_precise_mbclen((char *)s, (char *)send, e1); + if (!MBCLEN_CHARFOUND_P(r)) { + xfree(buf); + rb_raise(rb_eArgError, "invalid byte sequence in %s", rb_enc_name(e1)); + } + clen = MBCLEN_CHARFOUND_LEN(r); + c0 = c = rb_enc_mbc_to_codepoint((char *)s, (char *)send, e1); + tlen = enc == e1 ? clen : rb_enc_codelen(c, enc); s += clen; @@ -7944,7 +8476,15 @@ tr_trans(VALUE str, VALUE src, VALUE repl, int sflag) while (s < send) { int may_modify = 0; - c0 = c = rb_enc_codepoint_len((char *)s, (char *)send, &clen, e1); + + int r = rb_enc_precise_mbclen((char *)s, (char *)send, e1); + if (!MBCLEN_CHARFOUND_P(r)) { + xfree(buf); + rb_raise(rb_eArgError, "invalid byte sequence in %s", rb_enc_name(e1)); + } + clen = MBCLEN_CHARFOUND_LEN(r); + c0 = c = rb_enc_mbc_to_codepoint((char *)s, (char *)send, e1); + tlen = enc == e1 ? clen : rb_enc_codelen(c, enc); if (c < 256) { @@ -8010,9 +8550,12 @@ tr_trans(VALUE str, VALUE src, VALUE repl, int sflag) * call-seq: * tr!(selector, replacements) -> self or nil * - * Like String#tr, but modifies +self+ in place. - * Returns +self+ if any changes were made, +nil+ otherwise. + * Like String#tr, except: + * + * - Performs substitutions in +self+ (not in a copy of +self+). + * - Returns +self+ if any modifications were made, +nil+ otherwise. * + * Related: {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE @@ -8048,17 +8591,15 @@ rb_str_tr_bang(VALUE str, VALUE src, VALUE repl) * * Arguments +selector+ and +replacements+ must be valid character selectors * (see {Character Selectors}[rdoc-ref:character_selectors.rdoc]), - * and may use any of its valid forms, including negation, ranges, and escaping: + * and may use any of its valid forms, including negation, ranges, and escapes: * - * # Negation. - * 'hello'.tr('^aeiou', '-') # => "-e--o" - * # Ranges. - * 'ibm'.tr('b-z', 'a-z') # => "hal" - * # Escapes. + * 'hello'.tr('^aeiou', '-') # => "-e--o" # Negation. + * 'ibm'.tr('b-z', 'a-z') # => "hal" # Range. * 'hel^lo'.tr('\^aeiou', '-') # => "h-l-l-" # Escaped leading caret. * 'i-b-m'.tr('b\-z', 'a-z') # => "ibabm" # Escaped embedded hyphen. * 'foo\\bar'.tr('ab\\', 'XYZ') # => "fooZYXr" # Escaped backslash. * + * Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. */ static VALUE @@ -8161,9 +8702,10 @@ tr_find(unsigned int c, const char table[TR_TABLE_SIZE], VALUE del, VALUE nodel) * call-seq: * delete!(*selectors) -> self or nil * - * Like String#delete, but modifies +self+ in place. - * Returns +self+ if any changes were made, +nil+ otherwise. + * Like String#delete, but modifies +self+ in place; + * returns +self+ if any characters were deleted, +nil+ otherwise. * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE @@ -8232,13 +8774,7 @@ rb_str_delete_bang(int argc, VALUE *argv, VALUE str) * call-seq: * delete(*selectors) -> new_string * - * Returns a copy of +self+ with characters specified by +selectors+ removed - * (see {Multiple Character Selectors}[rdoc-ref:character_selectors.rdoc@Multiple+Character+Selectors]): - * - * "hello".delete "l","lo" #=> "heo" - * "hello".delete "lo" #=> "he" - * "hello".delete "aeiou", "^e" #=> "hell" - * "hello".delete "ej-m" #=> "ho" + * :include: doc/string/delete.rdoc * */ @@ -8255,8 +8791,12 @@ rb_str_delete(int argc, VALUE *argv, VALUE str) * call-seq: * squeeze!(*selectors) -> self or nil * - * Like String#squeeze, but modifies +self+ in place. - * Returns +self+ if any changes were made, +nil+ otherwise. + * Like String#squeeze, except that: + * + * - Characters are squeezed in +self+ (not in a copy of +self+). + * - Returns +self+ if any changes are made, +nil+ otherwise. + * + * Related: See {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE @@ -8339,16 +8879,7 @@ rb_str_squeeze_bang(int argc, VALUE *argv, VALUE str) * call-seq: * squeeze(*selectors) -> new_string * - * Returns a copy of +self+ with characters specified by +selectors+ "squeezed" - * (see {Multiple Character Selectors}[rdoc-ref:character_selectors.rdoc@Multiple+Character+Selectors]): - * - * "Squeezed" means that each multiple-character run of a selected character - * is squeezed down to a single character; - * with no arguments given, squeezes all characters: - * - * "yellow moon".squeeze #=> "yelow mon" - * " now is the".squeeze(" ") #=> " now is the" - * "putters shoot balls".squeeze("m-z") #=> "puters shot balls" + * :include: doc/string/squeeze.rdoc * */ @@ -8365,10 +8896,12 @@ rb_str_squeeze(int argc, VALUE *argv, VALUE str) * call-seq: * tr_s!(selector, replacements) -> self or nil * - * Like String#tr_s, but modifies +self+ in place. - * Returns +self+ if any changes were made, +nil+ otherwise. + * Like String#tr_s, except: + * + * - Modifies +self+ in place (not a copy of +self+). + * - Returns +self+ if any changes were made, +nil+ otherwise. * - * Related: String#squeeze!. + * Related: {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE @@ -8380,16 +8913,21 @@ rb_str_tr_s_bang(VALUE str, VALUE src, VALUE repl) /* * call-seq: - * tr_s(selector, replacements) -> string + * tr_s(selector, replacements) -> new_string + * + * Like String#tr, except: + * + * - Also squeezes the modified portions of the translated string; + * see String#squeeze. + * - Returns the translated and squeezed string. * - * Like String#tr, but also squeezes the modified portions of the translated string; - * returns a new string (translated and squeezed). + * Examples: * * 'hello'.tr_s('l', 'r') #=> "hero" * 'hello'.tr_s('el', '-') #=> "h-o" * 'hello'.tr_s('el', 'hx') #=> "hhxo" * - * Related: String#squeeze. + * Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. * */ @@ -8406,23 +8944,7 @@ rb_str_tr_s(VALUE str, VALUE src, VALUE repl) * call-seq: * count(*selectors) -> integer * - * Returns the total number of characters in +self+ - * that are specified by the given +selectors+ - * (see {Multiple Character Selectors}[rdoc-ref:character_selectors.rdoc@Multiple+Character+Selectors]): - * - * a = "hello world" - * a.count "lo" #=> 5 - * a.count "lo", "o" #=> 2 - * a.count "hello", "^l" #=> 4 - * a.count "ej-m" #=> 4 - * - * "hello^world".count "\\^aeiou" #=> 4 - * "hello-world".count "a\\-eo" #=> 4 - * - * c = "hello world\\r\\n" - * c.count "\\" #=> 2 - * c.count "\\A" #=> 0 - * c.count "X-\\w" #=> 3 + * :include: doc/string/count.rdoc */ static VALUE @@ -8585,8 +9107,8 @@ literal_split_pattern(VALUE spat, split_type_t default_type) /* * call-seq: - * split(field_sep = $;, limit = nil) -> array - * split(field_sep = $;, limit = nil) {|substring| ... } -> self + * split(field_sep = $;, limit = 0) -> array_of_substrings + * split(field_sep = $;, limit = 0) {|substring| ... } -> self * * :include: doc/string/split.rdoc * @@ -8657,11 +9179,15 @@ rb_str_split_m(int argc, VALUE *argv, VALUE str) } } -#define SPLIT_STR(beg, len) (empty_count = split_string(result, str, beg, len, empty_count)) +#define SPLIT_STR(beg, len) ( \ + empty_count = split_string(result, str, beg, len, empty_count), \ + str_mod_check(str, str_start, str_len)) beg = 0; char *ptr = RSTRING_PTR(str); - char *eptr = RSTRING_END(str); + char *const str_start = ptr; + const long str_len = RSTRING_LEN(str); + char *const eptr = str_start + str_len; if (split_type == SPLIT_TYPE_AWK) { char *bptr = ptr; int skip = 1; @@ -8722,7 +9248,6 @@ rb_str_split_m(int argc, VALUE *argv, VALUE str) } } else if (split_type == SPLIT_TYPE_STRING) { - char *str_start = ptr; char *substr_start = ptr; char *sptr = RSTRING_PTR(spat); long slen = RSTRING_LEN(spat); @@ -8739,6 +9264,7 @@ rb_str_split_m(int argc, VALUE *argv, VALUE str) continue; } SPLIT_STR(substr_start - str_start, (ptr+end) - substr_start); + str_mod_check(spat, sptr, slen); ptr += end + slen; substr_start = ptr; if (!NIL_P(limit) && lim <= ++i) break; @@ -8746,7 +9272,6 @@ rb_str_split_m(int argc, VALUE *argv, VALUE str) beg = ptr - str_start; } else if (split_type == SPLIT_TYPE_CHARS) { - char *str_start = ptr; int n; if (result) result = rb_ary_new_capa(RSTRING_LEN(str)); @@ -9011,8 +9536,8 @@ rb_str_enumerate_lines(int argc, VALUE *argv, VALUE str, VALUE ary) /* * call-seq: - * each_line(line_sep = $/, chomp: false) {|substring| ... } -> self - * each_line(line_sep = $/, chomp: false) -> enumerator + * each_line(record_separator = $/, chomp: false) {|substring| ... } -> self + * each_line(record_separator = $/, chomp: false) -> enumerator * * :include: doc/string/each_line.rdoc * @@ -9027,11 +9552,53 @@ rb_str_each_line(int argc, VALUE *argv, VALUE str) /* * call-seq: - * lines(Line_sep = $/, chomp: false) -> array_of_strings + * lines(record_separator = $/, chomp: false) -> array_of_strings + * + * Returns substrings ("lines") of +self+ + * according to the given arguments: + * + * s = <<~EOT + * This is the first line. + * This is line two. + * + * This is line four. + * This is line five. + * EOT + * + * With the default argument values: + * + * $/ # => "\n" + * s.lines + * # => + * ["This is the first line.\n", + * "This is line two.\n", + * "\n", + * "This is line four.\n", + * "This is line five.\n"] + * + * With a different +record_separator+: + * + * record_separator = ' is ' + * s.lines(record_separator) + * # => + * ["This is ", + * "the first line.\nThis is ", + * "line two.\n\nThis is ", + * "line four.\nThis is ", + * "line five.\n"] + * + * With keyword argument +chomp+ as +true+, + * removes the trailing newline from each line: * - * Forms substrings ("lines") of +self+ according to the given arguments - * (see String#each_line for details); returns the lines in an array. + * s.lines(chomp: true) + * # => + * ["This is the first line.", + * "This is line two.", + * "", + * "This is line four.", + * "This is line five."] * + * Related: see {Converting to Non-String}[rdoc-ref:String@Converting+to+Non--5CString]. */ static VALUE @@ -9132,7 +9699,7 @@ rb_str_enumerate_chars(VALUE str, VALUE ary) /* * call-seq: - * each_char {|c| ... } -> self + * each_char {|char| ... } -> self * each_char -> enumerator * * :include: doc/string/each_char.rdoc @@ -9192,7 +9759,7 @@ rb_str_enumerate_codepoints(VALUE str, VALUE ary) /* * call-seq: - * each_codepoint {|integer| ... } -> self + * each_codepoint {|codepoint| ... } -> self * each_codepoint -> enumerator * * :include: doc/string/each_codepoint.rdoc @@ -9225,56 +9792,65 @@ static regex_t * get_reg_grapheme_cluster(rb_encoding *enc) { int encidx = rb_enc_to_index(enc); - regex_t *reg_grapheme_cluster = NULL; - static regex_t *reg_grapheme_cluster_utf8 = NULL; - /* synchronize */ - if (encidx == rb_utf8_encindex() && reg_grapheme_cluster_utf8) { - reg_grapheme_cluster = reg_grapheme_cluster_utf8; - } - if (!reg_grapheme_cluster) { - const OnigUChar source_ascii[] = "\\X"; - OnigErrorInfo einfo; - const OnigUChar *source = source_ascii; - size_t source_len = sizeof(source_ascii) - 1; - switch (encidx) { + const OnigUChar source_ascii[] = "\\X"; + const OnigUChar *source = source_ascii; + size_t source_len = sizeof(source_ascii) - 1; + + switch (encidx) { #define CHARS_16BE(x) (OnigUChar)((x)>>8), (OnigUChar)(x) #define CHARS_16LE(x) (OnigUChar)(x), (OnigUChar)((x)>>8) #define CHARS_32BE(x) CHARS_16BE((x)>>16), CHARS_16BE(x) #define CHARS_32LE(x) CHARS_16LE(x), CHARS_16LE((x)>>16) #define CASE_UTF(e) \ - case ENCINDEX_UTF_##e: { \ - static const OnigUChar source_UTF_##e[] = {CHARS_##e('\\'), CHARS_##e('X')}; \ - source = source_UTF_##e; \ - source_len = sizeof(source_UTF_##e); \ - break; \ - } - CASE_UTF(16BE); CASE_UTF(16LE); CASE_UTF(32BE); CASE_UTF(32LE); + case ENCINDEX_UTF_##e: { \ + static const OnigUChar source_UTF_##e[] = {CHARS_##e('\\'), CHARS_##e('X')}; \ + source = source_UTF_##e; \ + source_len = sizeof(source_UTF_##e); \ + break; \ + } + CASE_UTF(16BE); CASE_UTF(16LE); CASE_UTF(32BE); CASE_UTF(32LE); #undef CASE_UTF #undef CHARS_16BE #undef CHARS_16LE #undef CHARS_32BE #undef CHARS_32LE - } - int r = onig_new(®_grapheme_cluster, source, source + source_len, - ONIG_OPTION_DEFAULT, enc, OnigDefaultSyntax, &einfo); - if (r) { - UChar message[ONIG_MAX_ERROR_MESSAGE_LEN]; - onig_error_code_to_str(message, r, &einfo); - rb_fatal("cannot compile grapheme cluster regexp: %s", (char *)message); - } - if (encidx == rb_utf8_encindex()) { - reg_grapheme_cluster_utf8 = reg_grapheme_cluster; - } } + + regex_t *reg_grapheme_cluster; + OnigErrorInfo einfo; + int r = onig_new(®_grapheme_cluster, source, source + source_len, + ONIG_OPTION_DEFAULT, enc, OnigDefaultSyntax, &einfo); + if (r) { + UChar message[ONIG_MAX_ERROR_MESSAGE_LEN]; + onig_error_code_to_str(message, r, &einfo); + rb_fatal("cannot compile grapheme cluster regexp: %s", (char *)message); + } + return reg_grapheme_cluster; } +static regex_t * +get_cached_reg_grapheme_cluster(rb_encoding *enc) +{ + int encidx = rb_enc_to_index(enc); + static regex_t *reg_grapheme_cluster_utf8 = NULL; + + if (encidx == rb_utf8_encindex()) { + if (!reg_grapheme_cluster_utf8) { + reg_grapheme_cluster_utf8 = get_reg_grapheme_cluster(enc); + } + + return reg_grapheme_cluster_utf8; + } + + return NULL; +} + static VALUE rb_str_each_grapheme_cluster_size(VALUE str, VALUE args, VALUE eobj) { size_t grapheme_cluster_count = 0; - regex_t *reg_grapheme_cluster = NULL; rb_encoding *enc = get_encoding(str); const char *ptr, *end; @@ -9282,7 +9858,13 @@ rb_str_each_grapheme_cluster_size(VALUE str, VALUE args, VALUE eobj) return rb_str_length(str); } - reg_grapheme_cluster = get_reg_grapheme_cluster(enc); + bool cached_reg_grapheme_cluster = true; + regex_t *reg_grapheme_cluster = get_cached_reg_grapheme_cluster(enc); + if (!reg_grapheme_cluster) { + reg_grapheme_cluster = get_reg_grapheme_cluster(enc); + cached_reg_grapheme_cluster = false; + } + ptr = RSTRING_PTR(str); end = RSTRING_END(str); @@ -9295,6 +9877,10 @@ rb_str_each_grapheme_cluster_size(VALUE str, VALUE args, VALUE eobj) ptr += len; } + if (!cached_reg_grapheme_cluster) { + onig_free(reg_grapheme_cluster); + } + return SIZET2NUM(grapheme_cluster_count); } @@ -9302,7 +9888,6 @@ static VALUE rb_str_enumerate_grapheme_clusters(VALUE str, VALUE ary) { VALUE orig = str; - regex_t *reg_grapheme_cluster = NULL; rb_encoding *enc = get_encoding(str); const char *ptr0, *ptr, *end; @@ -9311,7 +9896,14 @@ rb_str_enumerate_grapheme_clusters(VALUE str, VALUE ary) } if (!ary) str = rb_str_new_frozen(str); - reg_grapheme_cluster = get_reg_grapheme_cluster(enc); + + bool cached_reg_grapheme_cluster = true; + regex_t *reg_grapheme_cluster = get_cached_reg_grapheme_cluster(enc); + if (!reg_grapheme_cluster) { + reg_grapheme_cluster = get_reg_grapheme_cluster(enc); + cached_reg_grapheme_cluster = false; + } + ptr0 = ptr = RSTRING_PTR(str); end = RSTRING_END(str); @@ -9323,6 +9915,11 @@ rb_str_enumerate_grapheme_clusters(VALUE str, VALUE ary) ENUM_ELEM(ary, rb_str_subseq(str, ptr-ptr0, len)); ptr += len; } + + if (!cached_reg_grapheme_cluster) { + onig_free(reg_grapheme_cluster); + } + RB_GC_GUARD(str); if (ary) return ary; @@ -9332,7 +9929,7 @@ rb_str_enumerate_grapheme_clusters(VALUE str, VALUE ary) /* * call-seq: - * each_grapheme_cluster {|gc| ... } -> self + * each_grapheme_cluster {|grapheme_cluster| ... } -> self * each_grapheme_cluster -> enumerator * * :include: doc/string/each_grapheme_cluster.rdoc @@ -9383,10 +9980,12 @@ chopped_length(VALUE str) * call-seq: * chop! -> self or nil * - * Like String#chop, but modifies +self+ in place; - * returns +nil+ if +self+ is empty, +self+ otherwise. + * Like String#chop, except that: * - * Related: String#chomp!. + * - Removes trailing characters from +self+ (not from a copy of +self+). + * - Returns +self+ if any characters are removed, +nil+ otherwise. + * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE @@ -9518,7 +10117,7 @@ chompped_length(VALUE str, VALUE rs) if (p[len-1] == newline && (rslen <= 1 || memcmp(rsptr, pp, rslen) == 0)) { - if (rb_enc_left_char_head(p, pp, e, enc) == pp) + if (at_char_boundary(p, pp, e, enc)) return len - rslen; RB_GC_GUARD(rs); } @@ -9563,9 +10162,12 @@ rb_str_chomp_string(VALUE str, VALUE rs) * call-seq: * chomp!(line_sep = $/) -> self or nil * - * Like String#chomp, but modifies +self+ in place; - * returns +nil+ if no modification made, +self+ otherwise. + * Like String#chomp, except that: + * + * - Removes trailing characters from +self+ (not from a copy of +self+). + * - Returns +self+ if any characters are removed, +nil+ otherwise. * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE @@ -9573,7 +10175,7 @@ rb_str_chomp_bang(int argc, VALUE *argv, VALUE str) { VALUE rs; str_modifiable(str); - if (RSTRING_LEN(str) == 0) return Qnil; + if (RSTRING_LEN(str) == 0 && argc < 2) return Qnil; rs = chomp_rs(argc, argv); if (NIL_P(rs)) return Qnil; return rb_str_chomp_string(str, rs); @@ -9596,6 +10198,22 @@ rb_str_chomp(int argc, VALUE *argv, VALUE str) return rb_str_subseq(str, 0, chompped_length(str, rs)); } +static void +tr_setup_table_multi(char table[TR_TABLE_SIZE], VALUE *tablep, VALUE *ctablep, + VALUE str, int num_selectors, VALUE *selectors) +{ + int i; + + for (i=0; i<num_selectors; i++) { + VALUE selector = selectors[i]; + rb_encoding *enc; + + StringValue(selector); + enc = rb_enc_check(str, selector); + tr_setup_table(selector, table, i==0, tablep, ctablep, enc); + } +} + static long lstrip_offset(VALUE str, const char *s, const char *e, rb_encoding *enc) { @@ -9619,18 +10237,39 @@ lstrip_offset(VALUE str, const char *s, const char *e, rb_encoding *enc) return s - start; } +static long +lstrip_offset_table(VALUE str, const char *s, const char *e, rb_encoding *enc, + char table[TR_TABLE_SIZE], VALUE del, VALUE nodel) +{ + const char *const start = s; + + if (!s || s >= e) return 0; + + /* remove leading characters in the table */ + while (s < e) { + int n; + unsigned int cc = rb_enc_codepoint_len(s, e, &n, enc); + + if (!tr_find(cc, table, del, nodel)) break; + s += n; + } + return s - start; +} + /* * call-seq: - * lstrip! -> self or nil + * lstrip!(*selectors) -> self or nil * - * Like String#lstrip, except that any modifications are made in +self+; - * returns +self+ if any modification are made, +nil+ otherwise. + * Like String#lstrip, except that: * - * Related: String#rstrip!, String#strip!. + * - Performs stripping in +self+ (not in a copy of +self+). + * - Returns +self+ if any characters are stripped, +nil+ otherwise. + * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE -rb_str_lstrip_bang(VALUE str) +rb_str_lstrip_bang(int argc, VALUE *argv, VALUE str) { rb_encoding *enc; char *start, *s; @@ -9639,7 +10278,17 @@ rb_str_lstrip_bang(VALUE str) str_modify_keep_cr(str); enc = STR_ENC_GET(str); RSTRING_GETMEM(str, start, olen); - loffset = lstrip_offset(str, start, start+olen, enc); + if (argc > 0) { + char table[TR_TABLE_SIZE]; + VALUE del = 0, nodel = 0; + + tr_setup_table_multi(table, &del, &nodel, str, argc, argv); + loffset = lstrip_offset_table(str, start, start+olen, enc, table, del, nodel); + } + else { + loffset = lstrip_offset(str, start, start+olen, enc); + } + if (loffset > 0) { long len = olen-loffset; s = start + loffset; @@ -9654,26 +10303,48 @@ rb_str_lstrip_bang(VALUE str) /* * call-seq: - * lstrip -> new_string + * lstrip(*selectors) -> new_string * * Returns a copy of +self+ with leading whitespace removed; * see {Whitespace in Strings}[rdoc-ref:String@Whitespace+in+Strings]: * * whitespace = "\x00\t\n\v\f\r " * s = whitespace + 'abc' + whitespace - * s # => "\u0000\t\n\v\f\r abc\u0000\t\n\v\f\r " - * s.lstrip # => "abc\u0000\t\n\v\f\r " + * # => "\u0000\t\n\v\f\r abc\u0000\t\n\v\f\r " + * s.lstrip + * # => "abc\u0000\t\n\v\f\r " + * + * If +selectors+ are given, removes characters of +selectors+ from the beginning of +self+: + * + * s = "---abc+++" + * s.lstrip("-") # => "abc+++" * - * Related: String#rstrip, String#strip. + * +selectors+ must be valid character selectors (see {Character Selectors}[rdoc-ref:character_selectors.rdoc]), + * and may use any of its valid forms, including negation, ranges, and escapes: + * + * "01234abc56789".lstrip("0-9") # "abc56789" + * "01234abc56789".lstrip("0-9", "^4-6") # "4abc56789" + * + * Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. */ static VALUE -rb_str_lstrip(VALUE str) +rb_str_lstrip(int argc, VALUE *argv, VALUE str) { char *start; long len, loffset; + RSTRING_GETMEM(str, start, len); - loffset = lstrip_offset(str, start, start+len, STR_ENC_GET(str)); + if (argc > 0) { + char table[TR_TABLE_SIZE]; + VALUE del = 0, nodel = 0; + + tr_setup_table_multi(table, &del, &nodel, str, argc, argv); + loffset = lstrip_offset_table(str, start, start+len, STR_ENC_GET(str), table, del, nodel); + } + else { + loffset = lstrip_offset(str, start, start+len, STR_ENC_GET(str)); + } if (loffset <= 0) return str_duplicate(rb_cString, str); return rb_str_subseq(str, loffset, len - loffset); } @@ -9707,18 +10378,44 @@ rstrip_offset(VALUE str, const char *s, const char *e, rb_encoding *enc) return e - t; } +static long +rstrip_offset_table(VALUE str, const char *s, const char *e, rb_encoding *enc, + char table[TR_TABLE_SIZE], VALUE del, VALUE nodel) +{ + const char *t; + char *tp; + + rb_str_check_dummy_enc(enc); + if (rb_enc_str_coderange(str) == ENC_CODERANGE_BROKEN) { + rb_raise(rb_eEncCompatError, "invalid byte sequence in %s", rb_enc_name(enc)); + } + if (!s || s >= e) return 0; + t = e; + + /* remove trailing characters in the table */ + while ((tp = rb_enc_prev_char(s, t, e, enc)) != NULL) { + unsigned int c = rb_enc_codepoint(tp, e, enc); + if (!tr_find(c, table, del, nodel)) break; + t = tp; + } + + return e - t; +} + /* * call-seq: - * rstrip! -> self or nil + * rstrip!(*selectors) -> self or nil + * + * Like String#rstrip, except that: * - * Like String#rstrip, except that any modifications are made in +self+; - * returns +self+ if any modification are made, +nil+ otherwise. + * - Performs stripping in +self+ (not in a copy of +self+). + * - Returns +self+ if any characters are stripped, +nil+ otherwise. * - * Related: String#lstrip!, String#strip!. + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE -rb_str_rstrip_bang(VALUE str) +rb_str_rstrip_bang(int argc, VALUE *argv, VALUE str) { rb_encoding *enc; char *start; @@ -9727,7 +10424,16 @@ rb_str_rstrip_bang(VALUE str) str_modify_keep_cr(str); enc = STR_ENC_GET(str); RSTRING_GETMEM(str, start, olen); - roffset = rstrip_offset(str, start, start+olen, enc); + if (argc > 0) { + char table[TR_TABLE_SIZE]; + VALUE del = 0, nodel = 0; + + tr_setup_table_multi(table, &del, &nodel, str, argc, argv); + roffset = rstrip_offset_table(str, start, start+olen, enc, table, del, nodel); + } + else { + roffset = rstrip_offset(str, start, start+olen, enc); + } if (roffset > 0) { long len = olen - roffset; @@ -9741,9 +10447,9 @@ rb_str_rstrip_bang(VALUE str) /* * call-seq: - * rstrip -> new_string + * rstrip(*selectors) -> new_string * - * Returns a copy of the receiver with trailing whitespace removed; + * Returns a copy of +self+ with trailing whitespace removed; * see {Whitespace in Strings}[rdoc-ref:String@Whitespace+in+Strings]: * * whitespace = "\x00\t\n\v\f\r " @@ -9751,11 +10457,22 @@ rb_str_rstrip_bang(VALUE str) * s # => "\u0000\t\n\v\f\r abc\u0000\t\n\v\f\r " * s.rstrip # => "\u0000\t\n\v\f\r abc" * - * Related: String#lstrip, String#strip. + * If +selectors+ are given, removes characters of +selectors+ from the end of +self+: + * + * s = "---abc+++" + * s.rstrip("+") # => "---abc" + * + * +selectors+ must be valid character selectors (see {Character Selectors}[rdoc-ref:character_selectors.rdoc]), + * and may use any of its valid forms, including negation, ranges, and escapes: + * + * "01234abc56789".rstrip("0-9") # "01234abc" + * "01234abc56789".rstrip("0-9", "^4-6") # "01234abc56" + * + * Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. */ static VALUE -rb_str_rstrip(VALUE str) +rb_str_rstrip(int argc, VALUE *argv, VALUE str) { rb_encoding *enc; char *start; @@ -9763,8 +10480,16 @@ rb_str_rstrip(VALUE str) enc = STR_ENC_GET(str); RSTRING_GETMEM(str, start, olen); - roffset = rstrip_offset(str, start, start+olen, enc); + if (argc > 0) { + char table[TR_TABLE_SIZE]; + VALUE del = 0, nodel = 0; + tr_setup_table_multi(table, &del, &nodel, str, argc, argv); + roffset = rstrip_offset_table(str, start, start+olen, enc, table, del, nodel); + } + else { + roffset = rstrip_offset(str, start, start+olen, enc); + } if (roffset <= 0) return str_duplicate(rb_cString, str); return rb_str_subseq(str, 0, olen-roffset); } @@ -9772,16 +10497,18 @@ rb_str_rstrip(VALUE str) /* * call-seq: - * strip! -> self or nil + * strip!(*selectors) -> self or nil + * + * Like String#strip, except that: * - * Like String#strip, except that any modifications are made in +self+; - * returns +self+ if any modification are made, +nil+ otherwise. + * - Any modifications are made to +self+. + * - Returns +self+ if any modification are made, +nil+ otherwise. * - * Related: String#lstrip!, String#strip!. + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE -rb_str_strip_bang(VALUE str) +rb_str_strip_bang(int argc, VALUE *argv, VALUE str) { char *start; long olen, loffset, roffset; @@ -9790,8 +10517,19 @@ rb_str_strip_bang(VALUE str) str_modify_keep_cr(str); enc = STR_ENC_GET(str); RSTRING_GETMEM(str, start, olen); - loffset = lstrip_offset(str, start, start+olen, enc); - roffset = rstrip_offset(str, start+loffset, start+olen, enc); + + if (argc > 0) { + char table[TR_TABLE_SIZE]; + VALUE del = 0, nodel = 0; + + tr_setup_table_multi(table, &del, &nodel, str, argc, argv); + loffset = lstrip_offset_table(str, start, start+olen, enc, table, del, nodel); + roffset = rstrip_offset_table(str, start+loffset, start+olen, enc, table, del, nodel); + } + else { + loffset = lstrip_offset(str, start, start+olen, enc); + roffset = rstrip_offset(str, start+loffset, start+olen, enc); + } if (loffset > 0 || roffset > 0) { long len = olen-roffset; @@ -9809,29 +10547,52 @@ rb_str_strip_bang(VALUE str) /* * call-seq: - * strip -> new_string + * strip(*selectors) -> new_string * - * Returns a copy of the receiver with leading and trailing whitespace removed; + * Returns a copy of +self+ with leading and trailing whitespace removed; * see {Whitespace in Strings}[rdoc-ref:String@Whitespace+in+Strings]: * * whitespace = "\x00\t\n\v\f\r " * s = whitespace + 'abc' + whitespace - * s # => "\u0000\t\n\v\f\r abc\u0000\t\n\v\f\r " + * # => "\u0000\t\n\v\f\r abc\u0000\t\n\v\f\r " * s.strip # => "abc" * - * Related: String#lstrip, String#rstrip. + * If +selectors+ are given, removes characters of +selectors+ from both ends of +self+: + * + * s = "---abc+++" + * s.strip("-+") # => "abc" + * s.strip("+-") # => "abc" + * + * +selectors+ must be valid character selectors (see {Character Selectors}[rdoc-ref:character_selectors.rdoc]), + * and may use any of its valid forms, including negation, ranges, and escapes: + * + * "01234abc56789".strip("0-9") # "abc" + * "01234abc56789".strip("0-9", "^4-6") # "4abc56" + * + * Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. */ static VALUE -rb_str_strip(VALUE str) +rb_str_strip(int argc, VALUE *argv, VALUE str) { char *start; long olen, loffset, roffset; rb_encoding *enc = STR_ENC_GET(str); RSTRING_GETMEM(str, start, olen); - loffset = lstrip_offset(str, start, start+olen, enc); - roffset = rstrip_offset(str, start+loffset, start+olen, enc); + + if (argc > 0) { + char table[TR_TABLE_SIZE]; + VALUE del = 0, nodel = 0; + + tr_setup_table_multi(table, &del, &nodel, str, argc, argv); + loffset = lstrip_offset_table(str, start, start+olen, enc, table, del, nodel); + roffset = rstrip_offset_table(str, start+loffset, start+olen, enc, table, del, nodel); + } + else { + loffset = lstrip_offset(str, start, start+olen, enc); + roffset = rstrip_offset(str, start+loffset, start+olen, enc); + } if (loffset <= 0 && roffset <= 0) return str_duplicate(rb_cString, str); return rb_str_subseq(str, loffset, olen-loffset-roffset); @@ -9840,11 +10601,11 @@ rb_str_strip(VALUE str) static VALUE scan_once(VALUE str, VALUE pat, long *start, int set_backref_str) { - VALUE result, match; - struct re_registers *regs; - int i; + VALUE result = Qnil; long end, pos = rb_pat_search(pat, str, *start, set_backref_str); if (pos >= 0) { + VALUE match; + struct re_registers *regs; if (BUILTIN_TYPE(pat) == T_STRING) { regs = NULL; end = pos + RSTRING_LEN(pat); @@ -9855,6 +10616,7 @@ scan_once(VALUE str, VALUE pat, long *start, int set_backref_str) pos = BEG(0); end = END(0); } + if (pos == end) { rb_encoding *enc = STR_ENC_GET(str); /* @@ -9869,61 +10631,36 @@ scan_once(VALUE str, VALUE pat, long *start, int set_backref_str) else { *start = end; } + if (!regs || regs->num_regs == 1) { result = rb_str_subseq(str, pos, end - pos); return result; } - result = rb_ary_new2(regs->num_regs); - for (i=1; i < regs->num_regs; i++) { - VALUE s = Qnil; - if (BEG(i) >= 0) { - s = rb_str_subseq(str, BEG(i), END(i)-BEG(i)); + else { + result = rb_ary_new2(regs->num_regs); + for (int i = 1; i < regs->num_regs; i++) { + VALUE s = Qnil; + if (BEG(i) >= 0) { + s = rb_str_subseq(str, BEG(i), END(i)-BEG(i)); + } + + rb_ary_push(result, s); } - rb_ary_push(result, s); } - return result; + RB_GC_GUARD(match); } - return Qnil; + + return result; } /* * call-seq: - * scan(string_or_regexp) -> array - * scan(string_or_regexp) {|matches| ... } -> self - * - * Matches a pattern against +self+; the pattern is: - * - * - +string_or_regexp+ itself, if it is a Regexp. - * - <tt>Regexp.quote(string_or_regexp)</tt>, if +string_or_regexp+ is a string. + * scan(pattern) -> array_of_results + * scan(pattern) {|result| ... } -> self * - * Iterates through +self+, generating a collection of matching results: - * - * - If the pattern contains no groups, each result is the - * matched string, <code>$&</code>. - * - If the pattern contains groups, each result is an array - * containing one entry per group. - * - * With no block given, returns an array of the results: - * - * s = 'cruel world' - * s.scan(/\w+/) # => ["cruel", "world"] - * s.scan(/.../) # => ["cru", "el ", "wor"] - * s.scan(/(...)/) # => [["cru"], ["el "], ["wor"]] - * s.scan(/(..)(..)/) # => [["cr", "ue"], ["l ", "wo"]] - * - * With a block given, calls the block with each result; returns +self+: - * - * s.scan(/\w+/) {|w| print "<<#{w}>> " } - * print "\n" - * s.scan(/(.)(.)/) {|x,y| print y, x } - * print "\n" - * - * Output: - * - * <<cruel>> <<world>> - * rceu lowlr + * :include: doc/string/scan.rdoc * */ @@ -9965,18 +10702,46 @@ rb_str_scan(VALUE str, VALUE pat) * call-seq: * hex -> integer * - * Interprets the leading substring of +self+ as a string of hexadecimal digits - * (with an optional sign and an optional <code>0x</code>) and returns the - * corresponding number; - * returns zero if there is no such leading substring: + * Interprets the leading substring of +self+ as hexadecimal, possibly signed; + * returns its value as an integer. + * + * The leading substring is interpreted as hexadecimal when it begins with: * - * '0x0a'.hex # => 10 - * '-1234'.hex # => -4660 - * '0'.hex # => 0 - * 'non-numeric'.hex # => 0 + * - One or more character representing hexadecimal digits + * (each in one of the ranges <tt>'0'..'9'</tt>, <tt>'a'..'f'</tt>, or <tt>'A'..'F'</tt>); + * the string to be interpreted ends at the first character that does not represent a hexadecimal digit: * - * Related: String#oct. + * 'f'.hex # => 15 + * '11'.hex # => 17 + * 'FFF'.hex # => 4095 + * 'fffg'.hex # => 4095 + * 'foo'.hex # => 15 # 'f' hexadecimal, 'oo' not. + * 'bar'.hex # => 186 # 'ba' hexadecimal, 'r' not. + * 'deadbeef'.hex # => 3735928559 * + * - <tt>'0x'</tt> or <tt>'0X'</tt>, followed by one or more hexadecimal digits: + * + * '0xfff'.hex # => 4095 + * '0xfffg'.hex # => 4095 + * + * Any of the above may prefixed with <tt>'-'</tt>, which negates the interpreted value: + * + * '-fff'.hex # => -4095 + * '-0xFFF'.hex # => -4095 + * + * For any substring not described above, returns zero: + * + * 'xxx'.hex # => 0 + * ''.hex # => 0 + * + * Note that, unlike #oct, this method interprets only hexadecimal, + * and not binary, octal, or decimal notations: + * + * '0b111'.hex # => 45329 + * '0o777'.hex # => 0 + * '0d999'.hex # => 55705 + * + * Related: See {Converting to Non-String}[rdoc-ref:String@Converting+to+Non--5CString]. */ static VALUE @@ -9990,20 +10755,79 @@ rb_str_hex(VALUE str) * call-seq: * oct -> integer * - * Interprets the leading substring of +self+ as a string of octal digits - * (with an optional sign) and returns the corresponding number; - * returns zero if there is no such leading substring: + * Interprets the leading substring of +self+ as octal, binary, decimal, or hexadecimal, possibly signed; + * returns their value as an integer. + * + * In brief: + * + * # Interpreted as octal. + * '777'.oct # => 511 + * '777x'.oct # => 511 + * '0777'.oct # => 511 + * '0o777'.oct # => 511 + * '-777'.oct # => -511 + * # Not interpreted as octal. + * '0b111'.oct # => 7 # Interpreted as binary. + * '0d999'.oct # => 999 # Interpreted as decimal. + * '0xfff'.oct # => 4095 # Interpreted as hexadecimal. + * + * The leading substring is interpreted as octal when it begins with: + * + * - One or more character representing octal digits + * (each in the range <tt>'0'..'7'</tt>); + * the string to be interpreted ends at the first character that does not represent an octal digit: + * + * '7'.oct @ => 7 + * '11'.oct # => 9 + * '777'.oct # => 511 + * '0777'.oct # => 511 + * '7778'.oct # => 511 + * '777x'.oct # => 511 + * + * - <tt>'0o'</tt>, followed by one or more octal digits: + * + * '0o777'.oct # => 511 + * '0o7778'.oct # => 511 + * + * The leading substring is _not_ interpreted as octal when it begins with: + * + * - <tt>'0b'</tt>, followed by one or more characters representing binary digits + * (each in the range <tt>'0'..'1'</tt>); + * the string to be interpreted ends at the first character that does not represent a binary digit. + * the string is interpreted as binary digits (base 2): * - * '123'.oct # => 83 - * '-377'.oct # => -255 - * '0377non-numeric'.oct # => 255 - * 'non-numeric'.oct # => 0 + * '0b111'.oct # => 7 + * '0b1112'.oct # => 7 * - * If +self+ starts with <tt>0</tt>, radix indicators are honored; - * see Kernel#Integer. + * - <tt>'0d'</tt>, followed by one or more characters representing decimal digits + * (each in the range <tt>'0'..'9'</tt>); + * the string to be interpreted ends at the first character that does not represent a decimal digit. + * the string is interpreted as decimal digits (base 10): * - * Related: String#hex. + * '0d999'.oct # => 999 + * '0d999x'.oct # => 999 * + * - <tt>'0x'</tt>, followed by one or more characters representing hexadecimal digits + * (each in one of the ranges <tt>'0'..'9'</tt>, <tt>'a'..'f'</tt>, or <tt>'A'..'F'</tt>); + * the string to be interpreted ends at the first character that does not represent a hexadecimal digit. + * the string is interpreted as hexadecimal digits (base 16): + * + * '0xfff'.oct # => 4095 + * '0xfffg'.oct # => 4095 + * + * Any of the above may prefixed with <tt>'-'</tt>, which negates the interpreted value: + * + * '-777'.oct # => -511 + * '-0777'.oct # => -511 + * '-0b111'.oct # => -7 + * '-0xfff'.oct # => -4095 + * + * For any substring not described above, returns zero: + * + * 'foo'.oct # => 0 + * ''.oct # => 0 + * + * Related: see {Converting to Non-String}[rdoc-ref:String@Converting+to+Non--5CString]. */ static VALUE @@ -10019,11 +10843,6 @@ rb_str_oct(VALUE str) static struct { rb_nativethread_lock_t lock; } crypt_mutex = {PTHREAD_MUTEX_INITIALIZER}; - -static void -crypt_mutex_initialize(void) -{ -} #endif /* @@ -10094,6 +10913,7 @@ rb_str_crypt(VALUE str, VALUE salt) struct crypt_data *data; # define CRYPT_END() ALLOCV_END(databuf) #else + char *tmp_buf; extern char *crypt(const char *, const char *); # define CRYPT_END() rb_nativethread_lock_unlock(&crypt_mutex.lock) #endif @@ -10128,7 +10948,6 @@ rb_str_crypt(VALUE str, VALUE salt) # endif res = crypt_r(s, saltp, data); #else - crypt_mutex_initialize(); rb_nativethread_lock_lock(&crypt_mutex.lock); res = crypt(s, saltp); #endif @@ -10137,8 +10956,21 @@ rb_str_crypt(VALUE str, VALUE salt) CRYPT_END(); rb_syserr_fail(err, "crypt"); } +#ifdef HAVE_CRYPT_R result = rb_str_new_cstr(res); CRYPT_END(); +#else + // We need to copy this buffer because it's static and we need to unlock the mutex + // before allocating a new object (the string to be returned). If we allocate while + // holding the lock, we could run GC which fires the VM barrier and causes a deadlock + // if other ractors are waiting on this lock. + size_t res_size = strlen(res)+1; + tmp_buf = ALLOCA_N(char, res_size); // should be small enough to alloca + memcpy(tmp_buf, res, res_size); + res = tmp_buf; + CRYPT_END(); + result = rb_str_new_cstr(res); +#endif return result; } @@ -10266,7 +11098,7 @@ rb_str_justify(int argc, VALUE *argv, VALUE str, char jflag) rb_raise(rb_eArgError, "argument too big"); } len += size; - res = str_new0(rb_cString, 0, len, termlen); + res = str_enc_new(rb_cString, 0, len, enc); p = RSTRING_PTR(res); if (flen <= 1) { memset(p, *f, llen); @@ -10302,7 +11134,7 @@ rb_str_justify(int argc, VALUE *argv, VALUE str, char jflag) } TERM_FILL(p, termlen); STR_SET_LEN(res, p-RSTRING_PTR(res)); - rb_enc_associate(res, enc); + if (argc == 2) cr = ENC_CODERANGE_AND(cr, ENC_CODERANGE(pad)); if (cr != ENC_CODERANGE_BROKEN) @@ -10315,12 +11147,10 @@ rb_str_justify(int argc, VALUE *argv, VALUE str, char jflag) /* * call-seq: - * ljust(size, pad_string = ' ') -> new_string + * ljust(width, pad_string = ' ') -> new_string * * :include: doc/string/ljust.rdoc * - * Related: String#rjust, String#center. - * */ static VALUE @@ -10331,12 +11161,10 @@ rb_str_ljust(int argc, VALUE *argv, VALUE str) /* * call-seq: - * rjust(size, pad_string = ' ') -> new_string + * rjust(width, pad_string = ' ') -> new_string * * :include: doc/string/rjust.rdoc * - * Related: String#ljust, String#center. - * */ static VALUE @@ -10352,8 +11180,6 @@ rb_str_rjust(int argc, VALUE *argv, VALUE str) * * :include: doc/string/center.rdoc * - * Related: String#ljust, String#rjust. - * */ static VALUE @@ -10364,7 +11190,7 @@ rb_str_center(int argc, VALUE *argv, VALUE str) /* * call-seq: - * partition(string_or_regexp) -> [head, match, tail] + * partition(pattern) -> [pre_match, first_match, post_match] * * :include: doc/string/partition.rdoc * @@ -10401,7 +11227,7 @@ rb_str_partition(VALUE str, VALUE sep) /* * call-seq: - * rpartition(sep) -> [head, match, tail] + * rpartition(pattern) -> [pre_match, last_match, post_match] * * :include: doc/string/rpartition.rdoc * @@ -10441,7 +11267,7 @@ rb_str_rpartition(VALUE str, VALUE sep) /* * call-seq: - * start_with?(*string_or_regexp) -> true or false + * start_with?(*patterns) -> true or false * * :include: doc/string/start_with_p.rdoc * @@ -10459,10 +11285,20 @@ rb_str_start_with(int argc, VALUE *argv, VALUE str) return Qtrue; } else { + const char *p, *s, *e; + long slen, tlen; + rb_encoding *enc; + StringValue(tmp); - rb_enc_check(str, tmp); - if (RSTRING_LEN(str) < RSTRING_LEN(tmp)) continue; - if (memcmp(RSTRING_PTR(str), RSTRING_PTR(tmp), RSTRING_LEN(tmp)) == 0) + enc = rb_enc_check(str, tmp); + if ((tlen = RSTRING_LEN(tmp)) == 0) return Qtrue; + if ((slen = RSTRING_LEN(str)) < tlen) continue; + p = RSTRING_PTR(str); + e = p + slen; + s = p + tlen; + if (!at_char_right_boundary(p, s, e, enc)) + continue; + if (memcmp(p, RSTRING_PTR(tmp), tlen) == 0) return Qtrue; } } @@ -10481,12 +11317,13 @@ static VALUE rb_str_end_with(int argc, VALUE *argv, VALUE str) { int i; - char *p, *s, *e; - rb_encoding *enc; for (i=0; i<argc; i++) { VALUE tmp = argv[i]; + const char *p, *s, *e; long slen, tlen; + rb_encoding *enc; + StringValue(tmp); enc = rb_enc_check(str, tmp); if ((tlen = RSTRING_LEN(tmp)) == 0) return Qtrue; @@ -10494,9 +11331,9 @@ rb_str_end_with(int argc, VALUE *argv, VALUE str) p = RSTRING_PTR(str); e = p + slen; s = e - tlen; - if (rb_enc_left_char_head(p, s, e, enc) != s) + if (!at_char_boundary(p, s, e, enc)) continue; - if (memcmp(s, RSTRING_PTR(tmp), RSTRING_LEN(tmp)) == 0) + if (memcmp(s, RSTRING_PTR(tmp), tlen) == 0) return Qtrue; } return Qfalse; @@ -10514,12 +11351,17 @@ rb_str_end_with(int argc, VALUE *argv, VALUE str) static long deleted_prefix_length(VALUE str, VALUE prefix) { - char *strptr, *prefixptr; + const char *strptr, *prefixptr; long olen, prefixlen; + rb_encoding *enc = rb_enc_get(str); StringValue(prefix); - if (is_broken_string(prefix)) return 0; - rb_enc_check(str, prefix); + + if (!is_broken_string(prefix) || + !rb_enc_asciicompat(enc) || + !rb_enc_asciicompat(rb_enc_get(prefix))) { + enc = rb_enc_check(str, prefix); + } /* return 0 if not start with prefix */ prefixlen = RSTRING_LEN(prefix); @@ -10529,6 +11371,19 @@ deleted_prefix_length(VALUE str, VALUE prefix) strptr = RSTRING_PTR(str); prefixptr = RSTRING_PTR(prefix); if (memcmp(strptr, prefixptr, prefixlen) != 0) return 0; + if (is_broken_string(prefix)) { + if (!is_broken_string(str)) { + /* prefix in a valid string cannot be broken */ + return 0; + } + const char *strend = strptr + olen; + const char *after_prefix = strptr + prefixlen; + if (!at_char_right_boundary(strptr, after_prefix, strend, enc)) { + /* prefix does not end at char-boundary */ + return 0; + } + } + /* prefix part in `str` also should be valid. */ return prefixlen; } @@ -10537,9 +11392,10 @@ deleted_prefix_length(VALUE str, VALUE prefix) * call-seq: * delete_prefix!(prefix) -> self or nil * - * Like String#delete_prefix, except that +self+ is modified in place. - * Returns +self+ if the prefix is removed, +nil+ otherwise. + * Like String#delete_prefix, except that +self+ is modified in place; + * returns +self+ if the prefix is removed, +nil+ otherwise. * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE @@ -10585,7 +11441,7 @@ rb_str_delete_prefix(VALUE str, VALUE prefix) static long deleted_suffix_length(VALUE str, VALUE suffix) { - char *strptr, *suffixptr, *s; + const char *strptr, *suffixptr; long olen, suffixlen; rb_encoding *enc; @@ -10600,9 +11456,10 @@ deleted_suffix_length(VALUE str, VALUE suffix) if (olen < suffixlen) return 0; strptr = RSTRING_PTR(str); suffixptr = RSTRING_PTR(suffix); - s = strptr + olen - suffixlen; - if (memcmp(s, suffixptr, suffixlen) != 0) return 0; - if (rb_enc_left_char_head(strptr, s, strptr + olen, enc) != s) return 0; + const char *strend = strptr + olen; + const char *before_suffix = strend - suffixlen; + if (memcmp(before_suffix, suffixptr, suffixlen) != 0) return 0; + if (!at_char_boundary(strptr, before_suffix, strend, enc)) return 0; return suffixlen; } @@ -10611,9 +11468,10 @@ deleted_suffix_length(VALUE str, VALUE suffix) * call-seq: * delete_suffix!(suffix) -> self or nil * - * Like String#delete_suffix, except that +self+ is modified in place. - * Returns +self+ if the suffix is removed, +nil+ otherwise. + * Like String#delete_suffix, except that +self+ is modified in place; + * returns +self+ if the suffix is removed, +nil+ otherwise. * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE @@ -10665,6 +11523,21 @@ rb_str_setter(VALUE val, ID id, VALUE *var) } static void +nil_setter_warning(ID id) +{ + rb_warn_deprecated("non-nil '%"PRIsVALUE"'", NULL, rb_id2str(id)); +} + +void +rb_deprecated_str_setter(VALUE val, ID id, VALUE *var) +{ + rb_str_setter(val, id, var); + if (!NIL_P(*var)) { + nil_setter_warning(id); + } +} + +static void rb_fs_setter(VALUE val, ID id, VALUE *var) { val = rb_fs_check(val); @@ -10674,7 +11547,7 @@ rb_fs_setter(VALUE val, ID id, VALUE *var) rb_id2str(id)); } if (!NIL_P(val)) { - rb_warn_deprecated("`$;'", NULL); + nil_setter_warning(id); } *var = val; } @@ -10692,14 +11565,30 @@ static VALUE rb_str_force_encoding(VALUE str, VALUE enc) { str_modifiable(str); - rb_enc_associate(str, rb_to_encoding(enc)); + + rb_encoding *encoding = rb_to_encoding(enc); + int idx = rb_enc_to_index(encoding); + + // If the encoding is unchanged, we do nothing. + if (ENCODING_GET(str) == idx) { + return str; + } + + rb_enc_associate_index(str, idx); + + // If the coderange was 7bit and the new encoding is ASCII-compatible + // we can keep the coderange. + if (ENC_CODERANGE(str) == ENC_CODERANGE_7BIT && encoding && rb_enc_asciicompat(encoding)) { + return str; + } + ENC_CODERANGE_CLEAR(str); return str; } /* * call-seq: - * b -> string + * b -> new_string * * :include: doc/string/b.rdoc * @@ -10709,11 +11598,11 @@ static VALUE rb_str_b(VALUE str) { VALUE str2; - if (FL_TEST(str, STR_NOEMBED)) { - str2 = str_alloc_heap(rb_cString); + if (STR_EMBED_P(str)) { + str2 = str_alloc_embed(rb_cString, RSTRING_LEN(str) + TERM_LEN(str)); } else { - str2 = str_alloc_embed(rb_cString, RSTRING_LEN(str) + TERM_LEN(str)); + str2 = str_alloc_heap(rb_cString); } str_replace_shared_without_enc(str2, str); @@ -10742,11 +11631,8 @@ rb_str_b(VALUE str) * call-seq: * valid_encoding? -> true or false * - * Returns +true+ if +self+ is encoded correctly, +false+ otherwise: + * :include: doc/string/valid_encoding_p.rdoc * - * "\xc2\xa1".force_encoding("UTF-8").valid_encoding? # => true - * "\xc2".force_encoding("UTF-8").valid_encoding? # => false - * "\x80".force_encoding("UTF-8").valid_encoding? # => false */ static VALUE @@ -10761,12 +11647,12 @@ rb_str_valid_encoding_p(VALUE str) * call-seq: * ascii_only? -> true or false * - * Returns +true+ if +self+ contains only ASCII characters, - * +false+ otherwise: + * Returns whether +self+ contains only ASCII characters: * * 'abc'.ascii_only? # => true * "abc\u{6666}".ascii_only? # => false * + * Related: see {Querying}[rdoc-ref:String@Querying]. */ static VALUE @@ -10827,7 +11713,7 @@ str_compat_and_valid(VALUE str, rb_encoding *enc) rb_encoding *e = STR_ENC_GET(str); if (cr == ENC_CODERANGE_7BIT ? rb_enc_mbminlen(enc) != 1 : enc != e) { rb_raise(rb_eEncCompatError, "incompatible character encodings: %s and %s", - rb_enc_name(enc), rb_enc_name(e)); + rb_enc_inspect_name(enc), rb_enc_inspect_name(e)); } } return str; @@ -10882,7 +11768,7 @@ enc_str_scrub(rb_encoding *enc, VALUE str, VALUE repl, int cr) encidx = rb_enc_to_index(enc); #define DEFAULT_REPLACE_CHAR(str) do { \ - static const char replace[sizeof(str)-1] = str; \ + RBIMPL_ATTR_NONSTRING() static const char replace[sizeof(str)-1] = str; \ rep = replace; replen = (int)sizeof(replace); \ } while (0) @@ -11098,8 +11984,8 @@ enc_str_scrub(rb_encoding *enc, VALUE str, VALUE repl, int cr) /* * call-seq: - * scrub(replacement_string = default_replacement) -> new_string - * scrub{|bytes| ... } -> new_string + * scrub(replacement_string = default_replacement_string) -> new_string + * scrub{|sequence| ... } -> new_string * * :include: doc/string/scrub.rdoc * @@ -11114,11 +12000,15 @@ str_scrub(int argc, VALUE *argv, VALUE str) /* * call-seq: - * scrub! -> self - * scrub!(replacement_string = default_replacement) -> self - * scrub!{|bytes| ... } -> self + * scrub!(replacement_string = default_replacement_string) -> self + * scrub!{|sequence| ... } -> self * - * Like String#scrub, except that any replacements are made in +self+. + * Like String#scrub, except that: + * + * - Any replacements are made in +self+. + * - Returns +self+. + * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. * */ static VALUE @@ -11153,34 +12043,8 @@ unicode_normalize_common(int argc, VALUE *argv, VALUE str, ID id) * call-seq: * unicode_normalize(form = :nfc) -> string * - * Returns a copy of +self+ with - * {Unicode normalization}[https://unicode.org/reports/tr15] applied. - * - * Argument +form+ must be one of the following symbols - * (see {Unicode normalization forms}[https://unicode.org/reports/tr15/#Norm_Forms]): - * - * - +:nfc+: Canonical decomposition, followed by canonical composition. - * - +:nfd+: Canonical decomposition. - * - +:nfkc+: Compatibility decomposition, followed by canonical composition. - * - +:nfkd+: Compatibility decomposition. - * - * The encoding of +self+ must be one of: - * - * - Encoding::UTF_8 - * - Encoding::UTF_16BE - * - Encoding::UTF_16LE - * - Encoding::UTF_32BE - * - Encoding::UTF_32LE - * - Encoding::GB18030 - * - Encoding::UCS_2BE - * - Encoding::UCS_4BE - * - * Examples: + * :include: doc/string/unicode_normalize.rdoc * - * "a\u0300".unicode_normalize # => "a" - * "\u00E0".unicode_normalize(:nfd) # => "a " - * - * Related: String#unicode_normalize!, String#unicode_normalized?. */ static VALUE rb_str_unicode_normalize(int argc, VALUE *argv, VALUE str) @@ -11193,9 +12057,9 @@ rb_str_unicode_normalize(int argc, VALUE *argv, VALUE str) * unicode_normalize!(form = :nfc) -> self * * Like String#unicode_normalize, except that the normalization - * is performed on +self+. + * is performed on +self+ (not on a copy of +self+). * - * Related String#unicode_normalized?. + * Related: see {Modifying}[rdoc-ref:String@Modifying]. * */ static VALUE @@ -11207,8 +12071,9 @@ rb_str_unicode_normalize_bang(int argc, VALUE *argv, VALUE str) /* call-seq: * unicode_normalized?(form = :nfc) -> true or false * - * Returns +true+ if +self+ is in the given +form+ of Unicode normalization, - * +false+ otherwise. + * Returns whether +self+ is in the given +form+ of Unicode normalization; + * see String#unicode_normalize. + * * The +form+ must be one of +:nfc+, +:nfd+, +:nfkc+, or +:nfkd+. * * Examples: @@ -11221,11 +12086,10 @@ rb_str_unicode_normalize_bang(int argc, VALUE *argv, VALUE str) * * Raises an exception if +self+ is not in a Unicode encoding: * - * s = "\xE0".force_encoding('ISO-8859-1') - * s.unicode_normalized? # Raises Encoding::CompatibilityError. - * - * Related: String#unicode_normalize, String#unicode_normalize!. + * s = "\xE0".force_encoding(Encoding::ISO_8859_1) + * s.unicode_normalized? # Raises Encoding::CompatibilityError * + * Related: see {Querying}[rdoc-ref:String@Querying]. */ static VALUE rb_str_unicode_normalized_p(int argc, VALUE *argv, VALUE str) @@ -11236,17 +12100,17 @@ rb_str_unicode_normalized_p(int argc, VALUE *argv, VALUE str) /********************************************************************** * Document-class: Symbol * - * Symbol objects represent named identifiers inside the Ruby interpreter. + * A +Symbol+ object represents a named identifier inside the Ruby interpreter. * - * You can create a \Symbol object explicitly with: + * You can create a +Symbol+ object explicitly with: * * - A {symbol literal}[rdoc-ref:syntax/literals.rdoc@Symbol+Literals]. * - * The same Symbol object will be + * The same +Symbol+ object will be * created for a given name or string for the duration of a program's * execution, regardless of the context or meaning of that name. Thus * if <code>Fred</code> is a constant in one context, a method in - * another, and a class in a third, the Symbol <code>:Fred</code> + * another, and a class in a third, the +Symbol+ <code>:Fred</code> * will be the same object in all three contexts. * * module One @@ -11289,18 +12153,18 @@ rb_str_unicode_normalized_p(int argc, VALUE *argv, VALUE str) * local_variables * # => [:seven] * - * Symbol objects are different from String objects in that - * Symbol objects represent identifiers, while String objects - * represent text or data. + * A +Symbol+ object differs from a String object in that + * a +Symbol+ object represents an identifier, while a String object + * represents text or data. * * == What's Here * - * First, what's elsewhere. \Class \Symbol: + * First, what's elsewhere. Class +Symbol+: * * - Inherits from {class Object}[rdoc-ref:Object@What-27s+Here]. * - Includes {module Comparable}[rdoc-ref:Comparable@What-27s+Here]. * - * Here, class \Symbol provides methods that are useful for: + * Here, class +Symbol+ provides methods that are useful for: * * - {Querying}[rdoc-ref:Symbol@Methods+for+Querying] * - {Comparing}[rdoc-ref:Symbol@Methods+for+Comparing] @@ -11357,9 +12221,9 @@ rb_str_unicode_normalized_p(int argc, VALUE *argv, VALUE str) /* * call-seq: - * symbol == object -> true or false + * self == other -> true or false * - * Returns +true+ if +object+ is the same object as +self+, +false+ otherwise. + * Returns whether +other+ is the same object as +self+. */ #define sym_equal rb_obj_equal @@ -11459,30 +12323,31 @@ sym_inspect(VALUE sym) } else { rb_encoding *enc = STR_ENC_GET(str); - RSTRING_GETMEM(str, ptr, len); + VALUE orig_str = str; + + len = RSTRING_LEN(orig_str); str = rb_enc_str_new(0, len + 1, enc); + + // Get data pointer after allocation + ptr = RSTRING_PTR(orig_str); dest = RSTRING_PTR(str); memcpy(dest + 1, ptr, len); + + RB_GC_GUARD(orig_str); } dest[0] = ':'; + + RUBY_ASSERT_BUILTIN_TYPE(str, T_STRING); + return str; } -/* - * call-seq: - * to_s -> string - * - * Returns a string representation of +self+ (not including the leading colon): - * - * :foo.to_s # => "foo" - * - * Related: Symbol#inspect, Symbol#name. - */ - VALUE rb_sym_to_s(VALUE sym) { - return str_new_shared(rb_cString, rb_sym2str(sym)); + VALUE str = str_new_shared(rb_cString, rb_sym2str(sym)); + FL_SET_RAW(str, STR_CHILLED_SYMBOL_TO_S); + return str; } VALUE @@ -11516,18 +12381,24 @@ sym_succ(VALUE sym) /* * call-seq: - * symbol <=> object -> -1, 0, +1, or nil + * self <=> other -> -1, 0, 1, or nil * - * If +object+ is a symbol, - * returns the equivalent of <tt>symbol.to_s <=> object.to_s</tt>: + * Compares +self+ and +other+, using String#<=>. * - * :bar <=> :foo # => -1 - * :foo <=> :foo # => 0 - * :foo <=> :bar # => 1 + * Returns: * - * Otherwise, returns +nil+: + * - <tt>self.to_s <=> other.to_s</tt>, if +other+ is a symbol. + * - +nil+, otherwise. * - * :foo <=> 'bar' # => nil + * Examples: + * + * :bar <=> :foo # => -1 + * :foo <=> :foo # => 0 + * :foo <=> :bar # => 1 + * :foo <=> 'bar' # => nil + * + * \Class \Symbol includes module Comparable, + * each of whose methods uses Symbol#<=> for comparison. * * Related: String#<=>. */ @@ -11577,9 +12448,9 @@ sym_casecmp_p(VALUE sym, VALUE other) /* * call-seq: - * symbol =~ object -> integer or nil + * self =~ other -> integer or nil * - * Equivalent to <tt>symbol.to_s =~ object</tt>, + * Equivalent to <tt>self.to_s =~ other</tt>, * including possible updates to global variables; * see String#=~. * @@ -11625,11 +12496,11 @@ sym_match_m_p(int argc, VALUE *argv, VALUE sym) /* * call-seq: - * symbol[index] -> string or nil - * symbol[start, length] -> string or nil - * symbol[range] -> string or nil - * symbol[regexp, capture = 0] -> string or nil - * symbol[substring] -> string or nil + * self[offset] -> string or nil + * self[offset, size] -> string or nil + * self[range] -> string or nil + * self[regexp, capture = 0] -> string or nil + * self[substring] -> string or nil * * Equivalent to <tt>symbol.to_s[]</tt>; see String#[]. * @@ -11670,7 +12541,7 @@ sym_empty(VALUE sym) /* * call-seq: - * upcase(*options) -> symbol + * upcase(mapping) -> symbol * * Equivalent to <tt>sym.to_s.upcase.to_sym</tt>. * @@ -11686,7 +12557,7 @@ sym_upcase(int argc, VALUE *argv, VALUE sym) /* * call-seq: - * downcase(*options) -> symbol + * downcase(mapping) -> symbol * * Equivalent to <tt>sym.to_s.downcase.to_sym</tt>. * @@ -11704,7 +12575,7 @@ sym_downcase(int argc, VALUE *argv, VALUE sym) /* * call-seq: - * capitalize(*options) -> symbol + * capitalize(mapping) -> symbol * * Equivalent to <tt>sym.to_s.capitalize.to_sym</tt>. * @@ -11720,7 +12591,7 @@ sym_capitalize(int argc, VALUE *argv, VALUE sym) /* * call-seq: - * swapcase(*options) -> symbol + * swapcase(mapping) -> symbol * * Equivalent to <tt>sym.to_s.swapcase.to_sym</tt>. * @@ -11783,7 +12654,7 @@ string_for_symbol(VALUE name) if (!RB_TYPE_P(name, T_STRING)) { VALUE tmp = rb_check_string_type(name); if (NIL_P(tmp)) { - rb_raise(rb_eTypeError, "%+"PRIsVALUE" is not a symbol", + rb_raise(rb_eTypeError, "%+"PRIsVALUE" is not a symbol nor a string", name); } name = tmp; @@ -11837,8 +12708,16 @@ rb_str_to_interned_str(VALUE str) VALUE rb_interned_str(const char *ptr, long len) { - struct RString fake_str; - return register_fstring(setup_fake_str(&fake_str, ptr, len, ENCINDEX_US_ASCII), TRUE); + struct RString fake_str = {RBASIC_INIT}; + int encidx = ENCINDEX_US_ASCII; + int coderange = ENC_CODERANGE_7BIT; + if (len > 0 && search_nonascii(ptr, ptr + len)) { + encidx = ENCINDEX_ASCII_8BIT; + coderange = ENC_CODERANGE_VALID; + } + VALUE str = setup_fake_str(&fake_str, ptr, len, encidx); + ENC_CODERANGE_SET(str, coderange); + return register_fstring(str, true, false); } VALUE @@ -11850,12 +12729,25 @@ rb_interned_str_cstr(const char *ptr) VALUE rb_enc_interned_str(const char *ptr, long len, rb_encoding *enc) { - if (UNLIKELY(rb_enc_autoload_p(enc))) { + if (enc != NULL && UNLIKELY(rb_enc_autoload_p(enc))) { + rb_enc_autoload(enc); + } + + struct RString fake_str = {RBASIC_INIT}; + return register_fstring(rb_setup_fake_str(&fake_str, ptr, len, enc), true, false); +} + +VALUE +rb_enc_literal_str(const char *ptr, long len, rb_encoding *enc) +{ + if (enc != NULL && UNLIKELY(rb_enc_autoload_p(enc))) { rb_enc_autoload(enc); } - struct RString fake_str; - return register_fstring(rb_setup_fake_str(&fake_str, ptr, len, enc), TRUE); + struct RString fake_str = {RBASIC_INIT}; + VALUE str = register_fstring(rb_setup_fake_str(&fake_str, ptr, len, enc), true, true); + RUBY_ASSERT(RB_OBJ_SHAREABLE_P(str) && (rb_gc_verify_shareable(str), 1)); + return str; } VALUE @@ -11864,16 +12756,44 @@ rb_enc_interned_str_cstr(const char *ptr, rb_encoding *enc) return rb_enc_interned_str(ptr, strlen(ptr), enc); } +#if USE_YJIT || USE_ZJIT +void +rb_jit_str_concat_codepoint(VALUE str, VALUE codepoint) +{ + if (RB_LIKELY(ENCODING_GET_INLINED(str) == rb_ascii8bit_encindex())) { + ssize_t code = RB_NUM2SSIZE(codepoint); + + if (RB_LIKELY(code >= 0 && code < 0xff)) { + rb_str_buf_cat_byte(str, (char) code); + return; + } + } + + rb_str_concat(str, codepoint); +} +#endif + +static int +fstring_set_class_i(VALUE *str, void *data) +{ + RBASIC_SET_CLASS(*str, rb_cString); + + return ST_CONTINUE; +} + void Init_String(void) { rb_cString = rb_define_class("String", rb_cObject); - assert(rb_vm_fstring_table()); - st_foreach(rb_vm_fstring_table(), fstring_set_class_i, rb_cString); + + rb_concurrent_set_foreach_with_replace(fstring_table_obj, fstring_set_class_i, NULL); + rb_include_module(rb_cString, rb_mComparable); rb_define_alloc_func(rb_cString, empty_str_alloc); + rb_define_singleton_method(rb_cString, "new", rb_str_s_new, -1); rb_define_singleton_method(rb_cString, "try_convert", rb_str_s_try_convert, 1); rb_define_method(rb_cString, "initialize", rb_str_init, -1); + rb_define_method(rb_cString, "replace", rb_str_replace, 1); rb_define_method(rb_cString, "initialize_copy", rb_str_replace, 1); rb_define_method(rb_cString, "<=>", rb_str_cmp_m, 1); rb_define_method(rb_cString, "==", rb_str_equal, 1); @@ -11904,7 +12824,6 @@ Init_String(void) rb_define_method(rb_cString, "byteindex", rb_str_byteindex_m, -1); rb_define_method(rb_cString, "rindex", rb_str_rindex_m, -1); rb_define_method(rb_cString, "byterindex", rb_str_byterindex_m, -1); - rb_define_method(rb_cString, "replace", rb_str_replace, 1); rb_define_method(rb_cString, "clear", rb_str_clear, 0); rb_define_method(rb_cString, "chr", rb_str_chr, 0); rb_define_method(rb_cString, "getbyte", rb_str_getbyte, 1); @@ -11916,6 +12835,7 @@ Init_String(void) rb_define_method(rb_cString, "freeze", rb_str_freeze, 0); rb_define_method(rb_cString, "+@", str_uplus, 0); rb_define_method(rb_cString, "-@", str_uminus, 0); + rb_define_method(rb_cString, "dup", rb_str_dup_m, 0); rb_define_alias(rb_cString, "dedup", "-@"); rb_define_method(rb_cString, "to_i", rb_str_to_i, -1); @@ -11952,6 +12872,7 @@ Init_String(void) rb_define_method(rb_cString, "reverse", rb_str_reverse, 0); rb_define_method(rb_cString, "reverse!", rb_str_reverse_bang, 0); rb_define_method(rb_cString, "concat", rb_str_concat_multi, -1); + rb_define_method(rb_cString, "append_as_bytes", rb_str_append_as_bytes, -1); rb_define_method(rb_cString, "<<", rb_str_concat, 1); rb_define_method(rb_cString, "prepend", rb_str_prepend_multi, -1); rb_define_method(rb_cString, "crypt", rb_str_crypt, 1); @@ -11973,9 +12894,9 @@ Init_String(void) rb_define_method(rb_cString, "gsub", rb_str_gsub, -1); rb_define_method(rb_cString, "chop", rb_str_chop, 0); rb_define_method(rb_cString, "chomp", rb_str_chomp, -1); - rb_define_method(rb_cString, "strip", rb_str_strip, 0); - rb_define_method(rb_cString, "lstrip", rb_str_lstrip, 0); - rb_define_method(rb_cString, "rstrip", rb_str_rstrip, 0); + rb_define_method(rb_cString, "strip", rb_str_strip, -1); + rb_define_method(rb_cString, "lstrip", rb_str_lstrip, -1); + rb_define_method(rb_cString, "rstrip", rb_str_rstrip, -1); rb_define_method(rb_cString, "delete_prefix", rb_str_delete_prefix, 1); rb_define_method(rb_cString, "delete_suffix", rb_str_delete_suffix, 1); @@ -11983,9 +12904,9 @@ Init_String(void) rb_define_method(rb_cString, "gsub!", rb_str_gsub_bang, -1); rb_define_method(rb_cString, "chop!", rb_str_chop_bang, 0); rb_define_method(rb_cString, "chomp!", rb_str_chomp_bang, -1); - rb_define_method(rb_cString, "strip!", rb_str_strip_bang, 0); - rb_define_method(rb_cString, "lstrip!", rb_str_lstrip_bang, 0); - rb_define_method(rb_cString, "rstrip!", rb_str_rstrip_bang, 0); + rb_define_method(rb_cString, "strip!", rb_str_strip_bang, -1); + rb_define_method(rb_cString, "lstrip!", rb_str_lstrip_bang, -1); + rb_define_method(rb_cString, "rstrip!", rb_str_rstrip_bang, -1); rb_define_method(rb_cString, "delete_prefix!", rb_str_delete_prefix_bang, 1); rb_define_method(rb_cString, "delete_suffix!", rb_str_delete_suffix_bang, 1); @@ -12043,9 +12964,6 @@ Init_String(void) rb_define_method(rb_cSymbol, "==", sym_equal, 1); rb_define_method(rb_cSymbol, "===", sym_equal, 1); rb_define_method(rb_cSymbol, "inspect", sym_inspect, 0); - rb_define_method(rb_cSymbol, "to_s", rb_sym_to_s, 0); - rb_define_method(rb_cSymbol, "id2name", rb_sym_to_s, 0); - rb_define_method(rb_cSymbol, "name", rb_sym2str, 0); /* in symbol.c */ rb_define_method(rb_cSymbol, "to_proc", rb_sym_to_proc, 0); /* in proc.c */ rb_define_method(rb_cSymbol, "succ", sym_succ, 0); rb_define_method(rb_cSymbol, "next", sym_succ, 0); @@ -12073,3 +12991,4 @@ Init_String(void) rb_define_method(rb_cSymbol, "encoding", sym_encoding, 0); } + |
