diff options
Diffstat (limited to 'string.c')
| -rw-r--r-- | string.c | 2674 |
1 files changed, 1242 insertions, 1432 deletions
@@ -28,6 +28,7 @@ #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" @@ -44,8 +45,11 @@ #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 @@ -142,27 +146,7 @@ VALUE rb_cSymbol; RSTRING(str)->len = (n); \ } while (0) -static inline bool -str_encindex_fastpath(int encindex) -{ - // The overwhelming majority of strings are in one of these 3 encodings. - switch (encindex) { - case ENCINDEX_ASCII_8BIT: - case ENCINDEX_UTF_8: - case ENCINDEX_US_ASCII: - return true; - default: - return false; - } -} - -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_LEN(str) (rb_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);\ const int term_fill_len = (termlen);\ @@ -201,6 +185,7 @@ str_enc_fastpath(VALUE 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); \ @@ -236,9 +221,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 @@ -246,28 +233,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); } - if (FL_TEST_RAW(str, STR_PRECOMPUTED_HASH)) { - real_size += sizeof(st_index_t); - } - 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); @@ -354,8 +343,6 @@ mustnot_wchar(VALUE str) } } -static int fstring_cmp(VALUE a, VALUE b); - static VALUE register_fstring(VALUE str, bool copy, bool force_precompute_hash); #if SIZEOF_LONG == SIZEOF_VOIDP @@ -363,27 +350,11 @@ static VALUE register_fstring(VALUE str, bool copy, bool force_precompute_hash); #else #endif -#ifdef PRECOMPUTED_FAKESTR_HASH -static st_index_t -fstring_hash(VALUE str) +static inline bool +BARE_STRING_P(VALUE str) { - 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 rb_hash_end(rb_hash_uint32(h, (uint32_t)ENCODING_GET_INLINED(str))); + return RBASIC_CLASS(str) == rb_cString && !rb_shape_obj_has_ivars(str); } -#else -#define fstring_hash rb_str_hash -#endif - -#define BARE_STRING_P(str) (!FL_ANY_RAW(str, FL_EXIVAR) && RBASIC_CLASS(str) == rb_cString) static inline st_index_t str_do_hash(VALUE str) @@ -415,14 +386,91 @@ str_store_precomputed_hash(VALUE str, st_index_t hash) return str; } -struct fstr_update_arg { +VALUE +rb_fstring(VALUE str) +{ + VALUE fstr; + int bare; + + Check_Type(str, T_STRING); + + if (FL_TEST(str, RSTRING_FSTR)) + return str; + + bare = BARE_STRING_P(str); + if (!bare) { + if (STR_EMBED_P(str)) { + OBJ_FREEZE(str); + return str; + } + + if (FL_TEST_RAW(str, STR_SHARED_ROOT | STR_SHARED) == STR_SHARED_ROOT) { + RUBY_ASSERT(OBJ_FROZEN(str)); + return str; + } + } + + if (!FL_TEST_RAW(str, FL_FREEZE | STR_NOFREE | STR_CHILLED)) + rb_str_resize(str, RSTRING_LEN(str)); + + fstr = register_fstring(str, false, false); + + if (!bare) { + str_replace_shared_without_enc(str, fstr); + OBJ_FREEZE(str); + return str; + } + return fstr; +} + +static VALUE fstring_table_obj; + +static VALUE +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) +{ + long alen, blen; + const char *aptr, *bptr; + + 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 -build_fstring(VALUE str, struct fstr_update_arg *arg) +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); @@ -475,384 +523,38 @@ build_fstring(VALUE str, struct fstr_update_arg *arg) ENC_CODERANGE_SET(str, coderange); RBASIC(str)->flags |= RSTRING_FSTR; - + if (!RB_OBJ_SHAREABLE_P(str)) { + RB_OBJ_SET_SHAREABLE(str); + } + 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(!FL_TEST_RAW(str, FL_EXIVAR)); + RUBY_ASSERT(!rb_shape_obj_has_ivars(str)); RUBY_ASSERT(RBASIC_CLASS(str) == rb_cString); RUBY_ASSERT(!rb_objspace_garbage_object_p(str)); return str; } -VALUE -rb_fstring(VALUE str) -{ - VALUE fstr; - int bare; - - Check_Type(str, T_STRING); - - if (FL_TEST(str, RSTRING_FSTR)) - return str; - - bare = BARE_STRING_P(str); - if (!bare) { - if (STR_EMBED_P(str)) { - OBJ_FREEZE(str); - return str; - } - - if (FL_TEST_RAW(str, STR_SHARED_ROOT | STR_SHARED) == STR_SHARED_ROOT) { - RUBY_ASSERT(OBJ_FROZEN(str)); - return str; - } - } - - if (!FL_TEST_RAW(str, FL_FREEZE | STR_NOFREE | STR_CHILLED)) - rb_str_resize(str, RSTRING_LEN(str)); - - fstr = register_fstring(str, false, false); - - if (!bare) { - str_replace_shared_without_enc(str, fstr); - OBJ_FREEZE(str); - return str; - } - return fstr; -} - -#define FSTRING_TABLE_EMPTY Qfalse -#define FSTRING_TABLE_TOMBSTONE Qtrue -#define FSTRING_TABLE_MOVED Qundef - -struct fstring_table_entry { - VALUE str; - VALUE hash; -}; - -struct fstring_table_struct { - struct fstring_table_entry *entries; - unsigned int capacity; - unsigned int deleted_entries; - rb_atomic_t count; // TODO: pad to own cache line? -}; - -static void -fstring_table_free(void *ptr) -{ - struct fstring_table_struct *table = ptr; - xfree(table->entries); -} - -static size_t -fstring_table_size(const void *ptr) -{ - const struct fstring_table_struct *table = ptr; - return sizeof(struct fstring_table_struct) + sizeof(struct fstring_table_entry) * table->capacity; -} - -// We declare a type for the table so that we can lean on Ruby's GC for deferred reclamation -static const rb_data_type_t fstring_table_type = { - .wrap_struct_name = "VM/fstring_table", - .function = { - .dmark = NULL, - .dfree = fstring_table_free, - .dsize = fstring_table_size, - }, - .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE +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, }; - -static VALUE fstring_table_obj; - -static VALUE -new_fstring_table(int capacity) -{ - VALUE obj; - struct fstring_table_struct *table; - obj = TypedData_Make_Struct(0, struct fstring_table_struct, &fstring_table_type, table); - table->capacity = capacity; - table->count = 0; - table->entries = ZALLOC_N(struct fstring_table_entry, capacity); - return obj; -} - void Init_fstring_table(void) { - fstring_table_obj = new_fstring_table(8192); + fstring_table_obj = rb_concurrent_set_new(&fstring_concurrent_set_funcs, 8192); rb_gc_register_address(&fstring_table_obj); } -#if 0 - -// Linear probe -struct fstring_table_probe { - int idx; - int mask; -}; - -static int -fstring_table_probe_start(struct fstring_table_probe *probe, struct fstring_table_struct *table, VALUE hash_code) -{ - RUBY_ASSERT((table->capacity & (table->capacity - 1)) == 0); - probe->mask = table->capacity - 1; - probe->idx = hash_code & probe->mask; - return probe->idx; -} - -static int -fstring_table_probe_next(struct fstring_table_probe *probe) -{ - probe->idx = (probe->idx + 1) & probe->mask; - return probe->idx; -} - -#else - -// Struct containing probe information. Intended that the compiler should always inline this -// Quadratic probing -struct fstring_table_probe { - int idx; - int d; - int mask; -}; - -static int -fstring_table_probe_start(struct fstring_table_probe *probe, struct fstring_table_struct *table, VALUE hash_code) -{ - RUBY_ASSERT((table->capacity & (table->capacity - 1)) == 0); - probe->d = 0; - probe->mask = table->capacity - 1; - probe->idx = hash_code & probe->mask; - return probe->idx; -} - -static int -fstring_table_probe_next(struct fstring_table_probe *probe) -{ - probe->d++; - probe->idx = (probe->idx + probe->d) & probe->mask; - return probe->idx; -} -#endif - -#define RUBY_ATOMIC_VALUE_LOAD(x) (VALUE)(RUBY_ATOMIC_PTR_LOAD(x)) - -static void -fstring_insert_on_resize(struct fstring_table_struct *table, VALUE hash_code, VALUE value) -{ - struct fstring_table_probe probe; - int idx = fstring_table_probe_start(&probe, table, hash_code); - - for (;;) { - struct fstring_table_entry *entry = &table->entries[idx]; - VALUE candidate = entry->str; - - RUBY_ASSERT(candidate != FSTRING_TABLE_TOMBSTONE); - RUBY_ASSERT(candidate != FSTRING_TABLE_MOVED); - - if (candidate == FSTRING_TABLE_EMPTY) { - table->count++; - - RUBY_ASSERT(table->count < table->capacity / 2); - RUBY_ASSERT(entry->hash == 0); - - entry->str = value; - entry->hash = hash_code; - return; - } - - idx = fstring_table_probe_next(&probe); - } -} - -// Rebuilds the table -static void -fstring_try_resize(VALUE old_table_obj) -{ - RB_VM_LOCK_ENTER(); - - // Check if another thread has already resized - if (RUBY_ATOMIC_VALUE_LOAD(fstring_table_obj) != old_table_obj) { - goto end; - } - - struct fstring_table_struct *old_table = RTYPEDDATA_GET_DATA(old_table_obj); - - // This may overcount by up to the number of threads concurrently attempting to insert - // GC may also happen between now and the table being rebuilt - int expected_count = RUBY_ATOMIC_LOAD(old_table->count) - old_table->deleted_entries; - - struct fstring_table_entry *old_entries = old_table->entries; - int old_capacity = old_table->capacity; - int new_capacity = old_capacity * 2; - if (new_capacity > expected_count * 8) { - new_capacity = old_capacity / 2; - } - else if (new_capacity > expected_count * 4) { - new_capacity = old_capacity; - } - - // May cause GC and therefore deletes, so must hapen first - VALUE new_table_obj = new_fstring_table(new_capacity); - struct fstring_table_struct *new_table = RTYPEDDATA_GET_DATA(new_table_obj); - - for (int i = 0; i < old_capacity; i++) { - struct fstring_table_entry *entry = &old_entries[i]; - VALUE val = RUBY_ATOMIC_VALUE_EXCHANGE(entry->str, FSTRING_TABLE_MOVED); - RUBY_ASSERT(val != FSTRING_TABLE_MOVED); - if (val == FSTRING_TABLE_EMPTY) continue; - if (val == FSTRING_TABLE_TOMBSTONE) continue; - if (rb_objspace_garbage_object_p(val)) continue; - - VALUE hash_code = RUBY_ATOMIC_VALUE_LOAD(entry->hash); - if (hash_code == 0) { - // Either in-progress insert or extremely unlikely 0 hash - // Re-calculate the hash ourselves - hash_code = fstring_hash(val); - } - RUBY_ASSERT(hash_code == fstring_hash(val)); - fstring_insert_on_resize(new_table, hash_code, val); - } - -#if 0 - fprintf(stderr, "resized: %p(%i) -> %p(%i) (count: %i->%i)\n", old_table, old_table->capacity, new_table, new_table->capacity, old_table->count, new_table->count); -#endif - - RUBY_ATOMIC_VALUE_SET(fstring_table_obj, new_table_obj); - -end: - RB_GC_GUARD(old_table_obj); - RB_VM_LOCK_LEAVE(); -} - -static VALUE -fstring_find_or_insert(VALUE hash_code, VALUE value, struct fstr_update_arg *arg) -{ - struct fstring_table_probe probe; - bool inserting = false; - int idx; - VALUE table_obj; - struct fstring_table_struct *table; - - retry: - table_obj = RUBY_ATOMIC_VALUE_LOAD(fstring_table_obj); - RUBY_ASSERT(table_obj); - table = RTYPEDDATA_GET_DATA(table_obj); - idx = fstring_table_probe_start(&probe, table, hash_code); - - for (;;) { - struct fstring_table_entry *entry = &table->entries[idx]; - VALUE candidate = RUBY_ATOMIC_VALUE_LOAD(entry->str); - - if (candidate == FSTRING_TABLE_EMPTY) { - // Not in table - if (!inserting) { - // Prepare a string suitable for inserting into the table - value = build_fstring(value, arg); - RUBY_ASSERT(hash_code == fstring_hash(value)); - inserting = true; - } - - unsigned int prev_count = RUBY_ATOMIC_FETCH_ADD(table->count, 1); - - if (UNLIKELY(prev_count > table->capacity / 2)) { - fstring_try_resize(table_obj); - goto retry; - } - - VALUE found = RUBY_ATOMIC_VALUE_CAS(entry->str, FSTRING_TABLE_EMPTY, value); - if (found == FSTRING_TABLE_EMPTY) { - // Success! Our value was inserted - - // Also set the hash code - RUBY_ATOMIC_VALUE_SET(entry->hash, hash_code); - - RB_GC_GUARD(table_obj); - return value; - } - else { - // Nothing was inserted - RUBY_ATOMIC_DEC(table->count); // we didn't end up inserting - - // Another thread won the race, try again at the same location - continue; - } - } - else if (candidate == FSTRING_TABLE_TOMBSTONE) { - // Deleted entry, continue searching - } - else if (candidate == FSTRING_TABLE_MOVED) { - // Wait - RB_VM_LOCK_ENTER(); - RB_VM_LOCK_LEAVE(); - - goto retry; - } - else { - VALUE candidate_hash = RUBY_ATOMIC_VALUE_LOAD(entry->hash); - if ((candidate_hash == hash_code || candidate_hash == 0) && !fstring_cmp(candidate, value)) { - // We've found a match - if (UNLIKELY(rb_objspace_garbage_object_p(candidate))) { - // This is a weakref table, so after marking but before sweeping is complete we may find a matching garbage object. - // Skip it and mark it as a tombstone to help other threads out - RUBY_ATOMIC_VALUE_CAS(entry->str, candidate, FSTRING_TABLE_TOMBSTONE); - - // Fall through and continue our search - } - else { - RB_GC_GUARD(table_obj); - return candidate; - } - } - } - - idx = fstring_table_probe_next(&probe); - } -} - - -// Removes an fstring from the table. Compares by identity -static void -fstring_delete(VALUE hash_code, VALUE value) -{ - // Delete is never called concurrently, so atomic operations are unnecessary - VALUE table_obj = RUBY_ATOMIC_VALUE_LOAD(fstring_table_obj); - RUBY_ASSERT_ALWAYS(table_obj); - struct fstring_table_struct *table = RTYPEDDATA_GET_DATA(table_obj); - - struct fstring_table_probe probe; - int idx = fstring_table_probe_start(&probe, table, hash_code); - - for (;;) { - struct fstring_table_entry *entry = &table->entries[idx]; - VALUE candidate = entry->str; - - // Allocations should only occur at the beginning of the resize - RUBY_ASSERT(candidate != FSTRING_TABLE_MOVED); - - if (candidate == FSTRING_TABLE_EMPTY) { - // We didn't find our string to delete - return; - } - else if (candidate == value) { - // We found our string, replace it with a tombstone and increment the count - entry->str = FSTRING_TABLE_TOMBSTONE; - table->deleted_entries++; - return; - } - - idx = fstring_table_probe_next(&probe); - } -} - static VALUE register_fstring(VALUE str, bool copy, bool force_precompute_hash) { - struct fstr_update_arg args = { + struct fstr_create_arg args = { .copy = copy, .force_precompute_hash = force_precompute_hash }; @@ -865,60 +567,19 @@ register_fstring(VALUE str, bool copy, bool force_precompute_hash) } #endif - VALUE hash_code = fstring_hash(str); - VALUE result = fstring_find_or_insert(hash_code, str, &args); + 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(!FL_TEST_RAW(result, FL_EXIVAR)); RUBY_ASSERT(RBASIC_CLASS(result) == rb_cString); return result; } -void -rb_fstring_foreach_with_replace(st_foreach_check_callback_func *func, st_update_callback_func *replace, st_data_t arg) -{ - // Assume locking and barrier (which there is no assert for) - ASSERT_vm_locking(); - - VALUE table_obj = RUBY_ATOMIC_VALUE_LOAD(fstring_table_obj); - if (!table_obj) { - // Table not yet initialized. Nothing to iterate over - return; - } - struct fstring_table_struct *table = RTYPEDDATA_GET_DATA(table_obj); - - for (unsigned int i = 0; i < table->capacity; i++) { - VALUE key = table->entries[i].str; - if(key == FSTRING_TABLE_EMPTY) continue; - if(key == FSTRING_TABLE_TOMBSTONE) continue; - - enum st_retval retval; - retval = (*func)(key, key, arg, 0); - - if (retval == ST_REPLACE && replace) { - st_data_t value = key; - retval = (*replace)(&key, &value, arg, TRUE); - table->entries[i].str = key; - } - switch (retval) { - case ST_REPLACE: - case ST_CONTINUE: - break; - case ST_CHECK: - rb_bug("unsupported"); - case ST_STOP: - return; - case ST_DELETE: - table->entries[i].str = FSTRING_TABLE_TOMBSTONE; - break; - } - } -} - bool rb_obj_is_fstring_table(VALUE obj) { @@ -930,21 +591,32 @@ rb_obj_is_fstring_table(VALUE obj) void rb_gc_free_fstring(VALUE obj) { - // Assume locking and barrier (which there is no assert for) - ASSERT_vm_locking(); + ASSERT_vm_locking_with_barrier(); + + RUBY_ASSERT(FL_TEST(obj, RSTRING_FSTR)); + RUBY_ASSERT(OBJ_FROZEN(obj)); + RUBY_ASSERT(!FL_TEST(obj, STR_SHARED)); - VALUE str_hash = fstring_hash(obj); - fstring_delete(str_hash, obj); + 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; + RBASIC_SET_SHAPE_ID((VALUE)fake_str, ROOT_SHAPE_ID); if (!name) { RUBY_ASSERT_ALWAYS(len == 0); @@ -977,14 +649,14 @@ 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; + 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; + struct RString fake_str = {RBASIC_INIT}; return register_fstring(rb_setup_fake_str(&fake_str, ptr, len, enc), false, false); } @@ -994,22 +666,6 @@ rb_fstring_cstr(const char *ptr) return rb_fstring_new(ptr, strlen(ptr)); } -static int -fstring_cmp(VALUE a, VALUE b) -{ - long alen, blen; - const char *aptr, *bptr; - - 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); -} - static inline bool single_byte_optimizable(VALUE str) { @@ -1041,7 +697,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 @@ -1085,17 +741,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 } } @@ -1282,7 +940,7 @@ 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)); + return rb_str_encindex_fastpath(encindex) || rb_enc_asciicompat(rb_enc_get_from_index(encindex)); } int @@ -1337,13 +995,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); + 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; } @@ -1353,6 +1014,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; } @@ -1404,6 +1069,9 @@ str_enc_new(VALUE klass, const char *ptr, long len, rb_encoding *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); @@ -1873,7 +1541,7 @@ rb_str_tmp_frozen_no_embed_acquire(VALUE orig) * 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)) { + 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); } @@ -1884,6 +1552,10 @@ rb_str_tmp_frozen_no_embed_acquire(VALUE orig) 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; @@ -1901,8 +1573,8 @@ rb_str_tmp_frozen_release(VALUE orig, VALUE tmp) if (STR_EMBED_P(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)) { @@ -1933,6 +1605,7 @@ heap_str_make_shared(VALUE klass, VALUE 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)); @@ -1942,7 +1615,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; } @@ -1992,7 +1665,12 @@ str_new_frozen_buffer(VALUE klass, VALUE orig, int copy_encoding) 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); + } } } @@ -2194,13 +1872,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); + 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; } @@ -2210,6 +1890,9 @@ 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; } @@ -2235,8 +1918,8 @@ str_duplicate_setup_embed(VALUE klass, VALUE str, VALUE dup) long len = RSTRING_LEN(str); RUBY_ASSERT(STR_EMBED_P(dup)); - RUBY_ASSERT(str_embed_capa(dup) >= len + 1); - MEMCPY(RSTRING(dup)->as.embed.ary, RSTRING(str)->as.embed.ary, char, len + 1); + 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); } @@ -2249,7 +1932,7 @@ str_duplicate_setup_heap(VALUE klass, VALUE str, VALUE dup) if (FL_TEST_RAW(str, STR_SHARED)) { root = RSTRING(str)->as.heap.aux.shared; } - else if (UNLIKELY(!(flags & FL_FREEZE))) { + else if (UNLIKELY(!OBJ_FROZEN_RAW(str))) { root = str = str_new_frozen(klass, str); flags = FL_TEST_RAW(str, flag_mask); } @@ -2257,8 +1940,8 @@ str_duplicate_setup_heap(VALUE klass, VALUE str, VALUE dup) RUBY_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); + FL_SET_RAW(dup, RSTRING_NOEMBED); + STR_SET_SHARED(dup, root); flags |= RSTRING_NOEMBED | STR_SHARED; STR_SET_LEN(dup, RSTRING_LEN(str)); @@ -2301,7 +1984,7 @@ VALUE rb_str_dup_m(VALUE str) { if (LIKELY(BARE_STRING_P(str))) { - return str_duplicate(rb_obj_class(str), str); + return str_duplicate(rb_cString, str); } else { return rb_obj_dup(str); @@ -2346,9 +2029,14 @@ rb_str_with_debug_created_info(VALUE str, VALUE path, int line) } /* + * 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 * @@ -2750,12 +2438,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 @@ -2766,12 +2455,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 @@ -2837,13 +2527,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 @@ -2906,20 +2597,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 @@ -3083,7 +2776,7 @@ rb_must_asciicompat(VALUE str) rb_raise(rb_eTypeError, "not encoding capable object"); } - if (RB_LIKELY(str_encindex_fastpath(encindex))) { + if (RB_LIKELY(rb_str_encindex_fastpath(encindex))) { return; } @@ -3184,16 +2877,21 @@ str_null_check(VALUE str, int *w) { char *s = RSTRING_PTR(str); long len = RSTRING_LEN(str); - rb_encoding *enc = rb_enc_get(str); - const int minlen = rb_enc_mbminlen(enc); + int minlen = 1; + + if (RB_UNLIKELY(!rb_str_enc_fastpath(str))) { + rb_encoding *enc = rb_str_enc_get(str); + minlen = rb_enc_mbminlen(enc); - if (minlen > 1) { - *w = 1; - if (str_null_char(s, len, minlen, enc)) { - return NULL; + if (minlen > 1) { + *w = 1; + if (str_null_char(s, len, minlen, enc)) { + return NULL; + } + return str_fill_term(str, s, len, minlen); } - return str_fill_term(str, s, len, minlen); } + *w = 0; if (!s || memchr(s, 0, len)) { return NULL; @@ -3204,6 +2902,34 @@ str_null_check(VALUE str, int *w) return s; } +const char * +rb_str_null_check(VALUE str) +{ + RUBY_ASSERT(RB_TYPE_P(str, T_STRING)); + + char *s; + long len; + RSTRING_GETMEM(str, s, len); + + if (RB_LIKELY(rb_str_enc_fastpath(str))) { + if (!s || memchr(s, 0, len)) { + rb_raise(rb_eArgError, "string contains null byte"); + } + } + else { + int w; + const char *s = str_null_check(str, &w); + if (!s) { + if (w) { + rb_raise(rb_eArgError, "string contains null char"); + } + rb_raise(rb_eArgError, "string contains null byte"); + } + } + + return s; +} + char * rb_str_to_cstr(VALUE str) { @@ -3582,6 +3308,8 @@ rb_str_freeze(VALUE str) * 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) @@ -3596,24 +3324,37 @@ str_uplus(VALUE str) /* * call-seq: - * -string -> frozen_string - * dedup -> frozen_string + * -self -> frozen_string + * + * Returns a frozen string equal to +self+. + * + * The returned string is +self+ if and only if all of the following are true: + * + * - +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. + * + * Otherwise, the returned string is a frozen copy of +self+. * - * Returns a frozen, possibly pre-existing copy of the string. + * Returning +self+, when possible, saves duplicating +self+; + * see {Data deduplication}[https://en.wikipedia.org/wiki/Data_deduplication]. * - * 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. + * It may also save duplicating other, already-existing, strings: * - * Note that <tt>-string</tt> variant is more convenient for defining - * constants: + * s0 = 'foo' + * s1 = 'foo' + * s0.object_id == s1.object_id # => false + * (-s0).object_id == (-s1).object_id # => true * - * FILENAME = -'config/database.yml' + * Note that method #-@ is convenient for defining a constant: * - * while +dedup+ is better suitable for using the method in chains - * of calculations: + * FileName = -'config/database.yml' * - * @url_list.concat(urls.map(&:dedup)) + * 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) @@ -3630,6 +3371,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"); } @@ -3640,6 +3382,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"); } @@ -4035,7 +3778,7 @@ rb_str_buf_append(VALUE str, VALUE str2) { int str2_cr = rb_enc_str_coderange(str2); - if (str_enc_fastpath(str)) { + if (rb_str_enc_fastpath(str)) { switch (str2_cr) { case ENC_CODERANGE_7BIT: // If RHS is 7bit we can do simple concatenation @@ -4102,19 +3845,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) @@ -4139,25 +3870,27 @@ rb_str_concat_multi(int argc, VALUE *argv, VALUE str) /* * call-seq: - * append_as_bytes(*objects) -> string + * append_as_bytes(*objects) -> self * - * Concatenates each object in +objects+ into +self+ without any encoding - * validation or conversion and returns +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(" \xE2\x82") # => "foo \xE2\x82" + * s.valid_encoding? # => false * s.append_as_bytes("\xAC 12") - * s.valid_encoding? # => true + * s.valid_encoding? # => true * - * For each given object +object+ that is an Integer, - * the value is considered a Byte. If the Integer is bigger - * than one byte, only the lower byte is considered, similar to String#setbyte: + * 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.append_as_bytes(0, 257) # => "\u0000\u0001" + * s.bytesize # => 2 * - * Related: String#<<, String#concat, which do an encoding aware concatenation. + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ VALUE @@ -4262,7 +3995,7 @@ rb_str_append_as_bytes(int argc, VALUE *argv, VALUE str) clear_cr: // If no fast path was hit, we clear the coderange. - // append_as_bytes is predominently meant to be used in + // 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 @@ -4274,22 +4007,34 @@ rb_str_append_as_bytes(int argc, VALUE *argv, VALUE str) /* * call-seq: - * string << object -> string + * 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!" * - * If that codepoint is not representable in the encoding of - * _string_, RangeError is raised. + * 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> @@ -4297,14 +4042,7 @@ rb_str_append_as_bytes(int argc, VALUE *argv, VALUE str) * s = 'foo'.encode(Encoding::EUC_JP) * s << 0x00800080 # invalid codepoint 0x800080 in EUC-JP (RangeError) * - * If the encoding is US-ASCII and the codepoint is 0..0xff, _string_ - * is automatically promoted to ASCII-8BIT. - * - * s = 'foo'.encode(Encoding::US_ASCII) - * s << 0xff - * s.encoding # => #<Encoding:BINARY (ASCII-8BIT)> - * - * Related: String#concat, which takes multiple arguments. + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ VALUE rb_str_concat(VALUE str1, VALUE str2) @@ -4387,15 +4125,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 @@ -4449,10 +4186,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 @@ -4517,22 +4252,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(Encoding::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>. + * 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 @@ -4552,17 +4294,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(Encoding::ISO_8859_1).eql?("\u{c4 d6 dc}") # => false + * :include: doc/string/eql_p.rdoc * */ @@ -4576,24 +4308,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 @@ -4615,26 +4354,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 @@ -4699,9 +4438,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); } /* @@ -4709,22 +4448,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 @@ -4820,8 +4558,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 * @@ -4891,43 +4628,69 @@ str_ensure_byte_pos(VALUE str, long 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 Integer byte-based index of the first occurrence of the given +substring+, - * or +nil+ if none found: + * 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). * - * 'foo'.byteindex('f') # => 0 - * 'foo'.byteindex('o') # => 1 - * 'foo'.byteindex('oo') # => 1 - * 'foo'.byteindex('ooo') # => nil + * When +object+ is a string, + * returns the index of the first found substring equal to +object+: * - * Returns the Integer byte-based index of the first 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.byteindex('f') # => 0 + * s.byteindex('o') # => 1 + * s.byteindex('oo') # => 1 + * s.byteindex('ooo') # => nil * - * 'foo'.byteindex(/f/) # => 0 - * 'foo'.byteindex(/o/) # => 1 - * 'foo'.byteindex(/oo/) # => 1 - * 'foo'.byteindex(/ooo/) # => nil + * 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]: * - * Integer argument +offset+, if given, specifies the byte-based position in the - * string to begin the search: + * s = 'foo' + * s.byteindex(/f/) # => 0 + * $~ # => #<MatchData "f"> + * s.byteindex(/o/) # => 1 + * s.byteindex(/oo/) # => 1 + * s.byteindex(/ooo/) # => nil + * $~ # => nil * - * 'foo'.byteindex('o', 1) # => 1 - * 'foo'.byteindex('o', 2) # => 2 - * 'foo'.byteindex('o', 3) # => nil + * \Integer argument +offset+, if given, specifies the 0-based index + * of the byte where searching is to begin. * - * If +offset+ is negative, counts backward from the end of +self+: + * When +offset+ is non-negative, + * searching begins at byte position +offset+: * - * 'foo'.byteindex('o', -1) # => 2 - * 'foo'.byteindex('o', -2) # => 1 - * 'foo'.byteindex('o', -3) # => 1 - * 'foo'.byteindex('o', -4) # => nil + * s = 'foo' + * s.byteindex('o', 1) # => 1 + * s.byteindex('o', 2) # => 2 + * s.byteindex('o', 3) # => nil * - * If +offset+ does not land on character (codepoint) boundary, +IndexError+ is - * raised. + * When +offset+ is negative, counts backward from the end of +self+: * - * Related: String#index, String#byterindex. + * s = 'foo' + * s.byteindex('o', -1) # => 2 + * s.byteindex('o', -2) # => 1 + * s.byteindex('o', -3) # => 1 + * s.byteindex('o', -4) # => nil + * + * Raises IndexError if the byte at +offset+ is not the first byte of a character: + * + * 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: see {Querying}[rdoc-ref:String@Querying]. */ static VALUE @@ -5053,59 +4816,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"> + * rindex(pattern, offset = self.length) -> integer or nil * - * Or String#index with negative lookforward. + * :include:doc/string/rindex.rdoc * - * '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+: - * - * 'foo'.rindex('o', -1) # => 2 - * 'foo'.rindex('o', -2) # => 1 - * 'foo'.rindex('o', -3) # => nil - * 'foo'.rindex('o', -4) # => nil - * - * Related: String#index. */ static VALUE @@ -5183,65 +4897,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 * - * If +offset+ does not land on character (codepoint) boundary, +IndexError+ is - * raised. + * When +offset+ is negative, counts backward from the end of +self+: * - * Related: String#byteindex. + * s = 'foo' + * s.byterindex('o', -1) # => 2 + * s.byterindex('o', -2) # => 1 + * s.byterindex('o', -3) # => nil + * + * Raises IndexError if the byte at +offset+ is not the first byte of a character: + * + * 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 @@ -5285,30 +5024,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 * - * Returns the Integer index of the first substring that matches - * the given +regexp+, or +nil+ if no match found: + * When +other+ is a Regexp: + * + * - 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 @@ -5335,34 +5080,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+. - * - * Note: also updates Regexp@Global+Variables. + * Creates a MatchData object based on +self+ and the given arguments; + * updates {Regexp Global Variables}[rdoc-ref:Regexp@Global+Variables]. * * - 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 = regexp.match(self) - * - * With no block given, returns the computed +matchdata+: * - * '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 @@ -5384,24 +5131,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 @@ -5602,57 +5348,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 * */ @@ -5757,7 +5453,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 @@ -5790,33 +5488,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 * */ @@ -6057,15 +5729,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 * */ @@ -6280,28 +5950,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 * */ @@ -6323,19 +5978,9 @@ rb_str_aset_m(int argc, VALUE *argv, VALUE str) /* * call-seq: - * insert(index, other_string) -> self + * insert(offset, other_string) -> self * - * Inserts the given +other_string+ into +self+; returns +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 * */ @@ -6363,18 +6008,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 @@ -6548,13 +6195,12 @@ rb_pat_search(VALUE pat, VALUE str, long pos, int set_backref_str) * sub!(pattern, replacement) -> self or nil * sub!(pattern) {|match| ... } -> self or nil * - * Replaces the first occurrence (not all occurrences) of the given +pattern+ - * on +self+; returns +self+ if a replacement occurred, +nil+ otherwise. - * - * 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 @@ -6673,13 +6319,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 @@ -6759,7 +6399,7 @@ str_gsub(int argc, VALUE *argv, VALUE str, int bang) val = rb_obj_as_string(rb_yield(match0)); } else { - struct RString fake_str; + 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, @@ -6840,15 +6480,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. - * - * See {Substitution Methods}[rdoc-ref:String@Substitution+Methods]. - * - * Returns an Enumerator if no +replacement+ and no block given. + * Like String#gsub, except that: * - * 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 @@ -6865,14 +6502,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. * - * See {Substitution Methods}[rdoc-ref:String@Substitution+Methods]. + * 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. * - * Returns an Enumerator if no +replacement+ and no block given. + * Below are some simple examples; + * for many more examples, see {Substitution Methods}[rdoc-ref:String@Substitution+Methods]. * - * Related: String#sub, String#sub!, String#gsub!. + * 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. + * + * With argument +pattern+ and a block given, + * calls the block with each matching substring; + * replaces that substring with the block's return value: + * + * s.gsub(/[a-d]/) {|substring| substring.upcase } + * # => "ABrACADABrA" + * + * 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 @@ -6886,11 +6550,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 @@ -6910,9 +6576,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 @@ -6933,10 +6601,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 * */ @@ -6950,14 +6615,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: + * :include: doc/string/getbyte.rdoc * - * s = 'abcde' # => "abcde" - * s.getbyte(0) # => 97 - * s.getbyte(-1) # => 101 - * s.getbyte(5) # => nil - * - * Related: String#setbyte. */ VALUE rb_str_getbyte(VALUE str, VALUE index) @@ -6976,13 +6635,14 @@ 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]. */ VALUE rb_str_setbyte(VALUE str, VALUE index, VALUE value) @@ -7113,45 +6773,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 @@ -7191,23 +6816,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 - * - * 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. + * bytesplice(offset, length, str) -> self + * bytesplice(offset, length, str, str_offset, str_length) -> self + * bytesplice(range, str) -> self + * bytesplice(range, str, str_range) -> self * - * 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 @@ -7274,12 +6888,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 @@ -7339,10 +6957,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 @@ -7376,13 +6996,17 @@ rb_str_reverse_bang(VALUE str) * call-seq: * 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 @@ -7402,12 +7026,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 @@ -7427,6 +7052,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 @@ -7450,7 +7076,7 @@ rb_str_to_i(int argc, VALUE *argv, VALUE str) * '3.14159'.to_f # => 3.14159 * '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 * @@ -7458,6 +7084,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 @@ -7469,10 +7096,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+. + * + * Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. */ static VALUE @@ -7605,12 +7234,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 * */ @@ -7715,16 +7339,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 * */ @@ -7984,10 +7601,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"); } @@ -7995,6 +7608,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; @@ -8010,17 +7629,11 @@ static VALUE rb_str_is_ascii_only_p(VALUE str); /* * call-seq: - * undump -> string + * undump -> new_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 - * - * 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 @@ -8345,21 +7958,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 @@ -8387,18 +7993,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 @@ -8447,21 +8044,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 @@ -8489,17 +8079,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!" + * downcase(mapping = :ascii) -> new_string * - * The casing may be affected by the given +options+; - * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. - * - * Related: String#downcase!, String#upcase, String#upcase!. + * :include: doc/string/downcase.rdoc * */ @@ -8531,22 +8113,14 @@ rb_str_downcase(int argc, VALUE *argv, VALUE str) /* * call-seq: - * capitalize!(*options) -> self or nil - * - * Upcases the first character in +self+; - * downcases the remaining characters; - * returns +self+ if any changes were made, +nil+ otherwise: + * capitalize!(mapping = :ascii) -> self or nil * - * s = 'hello World!' # => "hello World!" - * s.capitalize! # => "Hello world!" - * s # => "Hello world!" - * s.capitalize! # => nil + * Like String#capitalize, except that: * - * 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 @@ -8571,19 +8145,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 * */ @@ -8610,22 +8174,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: - * - * s = 'Hello World!' # => "Hello World!" - * s.swapcase! # => "hELLO wORLD!" - * s # => "hELLO wORLD!" - * ''.swapcase! # => nil + * swapcase!(mapping) -> self or nil * - * The casing may be affected by the given +options+; - * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. + * Like String#swapcase, except that: * - * 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 @@ -8649,19 +8205,9 @@ rb_str_swapcase_bang(int argc, VALUE *argv, VALUE str) /* * call-seq: - * swapcase(*options) -> string + * swapcase(mapping = :ascii) -> new_string * - * Returns a string containing the characters in +self+, with cases reversed; - * each uppercase character is downcased; - * each lowercase character is upcased: - * - * 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 * */ @@ -9017,9 +8563,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 @@ -9055,17 +8604,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 @@ -9168,9 +8715,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 @@ -9239,13 +8787,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 * */ @@ -9262,8 +8804,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 @@ -9346,16 +8892,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 * */ @@ -9372,10 +8909,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 @@ -9387,16 +8926,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: * - * Like String#tr, but also squeezes the modified portions of the translated string; - * returns a new string (translated and squeezed). + * - Also squeezes the modified portions of the translated string; + * see String#squeeze. + * - Returns the translated and squeezed string. + * + * 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]. * */ @@ -9413,23 +8957,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 @@ -9592,7 +9120,7 @@ literal_split_pattern(VALUE spat, split_type_t default_type) /* * call-seq: - * split(field_sep = $;, limit = 0) -> array + * split(field_sep = $;, limit = 0) -> array_of_substrings * split(field_sep = $;, limit = 0) {|substring| ... } -> self * * :include: doc/string/split.rdoc @@ -9664,11 +9192,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; @@ -9729,7 +9261,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); @@ -9746,6 +9277,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; @@ -9753,7 +9285,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)); @@ -10018,8 +9549,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 * @@ -10034,11 +9565,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 * - * Forms substrings ("lines") of +self+ according to the given arguments - * (see String#each_line for details); returns the lines in an array. + * 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: + * + * 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 @@ -10139,7 +9712,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 @@ -10199,7 +9772,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 @@ -10369,7 +9942,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 @@ -10420,10 +9993,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: + * + * - Removes trailing characters from +self+ (not from a copy of +self+). + * - Returns +self+ if any characters are removed, +nil+ otherwise. * - * Related: String#chomp!. + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE @@ -10600,9 +10175,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 @@ -10633,6 +10211,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) { @@ -10656,18 +10250,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; @@ -10676,7 +10291,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; @@ -10691,26 +10316,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+: * - * Related: String#rstrip, String#strip. + * s = "---abc+++" + * s.lstrip("-") # => "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".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); } @@ -10744,18 +10391,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; @@ -10764,7 +10437,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; @@ -10778,9 +10460,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 " @@ -10788,11 +10470,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; @@ -10800,8 +10493,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); } @@ -10809,16 +10510,18 @@ rb_str_rstrip(VALUE str) /* * call-seq: - * strip! -> self or nil + * strip!(*selectors) -> self or nil * - * Like String#strip, except that any modifications are made in +self+; - * returns +self+ if any modification are made, +nil+ otherwise. + * Like String#strip, except that: * - * Related: String#lstrip!, String#strip!. + * - Any modifications are made to +self+. + * - Returns +self+ if any modification are made, +nil+ otherwise. + * + * 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; @@ -10827,8 +10530,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; @@ -10846,29 +10560,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); @@ -10933,40 +10670,10 @@ scan_once(VALUE str, VALUE pat, long *start, int set_backref_str) /* * 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. - * - * 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"]] + * scan(pattern) -> array_of_results + * scan(pattern) {|result| ... } -> self * - * 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 * */ @@ -11008,18 +10715,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: + * + * - 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: + * + * '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: * - * '0x0a'.hex # => 10 - * '-1234'.hex # => -4660 - * '0'.hex # => 0 - * 'non-numeric'.hex # => 0 + * 'xxx'.hex # => 0 + * ''.hex # => 0 * - * Related: String#oct. + * 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 @@ -11033,20 +10768,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): + * + * '0b111'.oct # => 7 + * '0b1112'.oct # => 7 * - * '123'.oct # => 83 - * '-377'.oct # => -255 - * '0377non-numeric'.oct # => 255 - * 'non-numeric'.oct # => 0 + * - <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): * - * If +self+ starts with <tt>0</tt>, radix indicators are honored; - * see Kernel#Integer. + * '0d999'.oct # => 999 + * '0d999x'.oct # => 999 * - * Related: String#hex. + * - <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 @@ -11062,11 +10856,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 /* @@ -11137,6 +10926,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 @@ -11171,7 +10961,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 @@ -11180,8 +10969,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; } @@ -11358,12 +11160,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 @@ -11374,12 +11174,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 @@ -11395,8 +11193,6 @@ rb_str_rjust(int argc, VALUE *argv, VALUE str) * * :include: doc/string/center.rdoc * - * Related: String#ljust, String#rjust. - * */ static VALUE @@ -11407,7 +11203,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 * @@ -11444,7 +11240,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 * @@ -11484,7 +11280,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 * @@ -11609,9 +11405,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 @@ -11684,9 +11481,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 @@ -11738,6 +11536,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); @@ -11747,7 +11560,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; } @@ -11788,7 +11601,7 @@ rb_str_force_encoding(VALUE str, VALUE enc) /* * call-seq: - * b -> string + * b -> new_string * * :include: doc/string/b.rdoc * @@ -11831,11 +11644,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(Encoding::UTF_8).valid_encoding? # => true - * "\xc2".force_encoding(Encoding::UTF_8).valid_encoding? # => false - * "\x80".force_encoding(Encoding::UTF_8).valid_encoding? # => false */ static VALUE @@ -11850,12 +11660,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 @@ -11971,7 +11781,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) @@ -12187,8 +11997,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 * @@ -12203,11 +12013,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: * - * Like String#scrub, except that any replacements are made in +self+. + * - Any replacements are made in +self+. + * - Returns +self+. + * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. * */ static VALUE @@ -12242,34 +12056,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 + * :include: doc/string/unicode_normalize.rdoc * - * Examples: - * - * "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) @@ -12282,9 +12070,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 @@ -12296,8 +12084,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: @@ -12311,10 +12100,9 @@ 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(Encoding::ISO_8859_1) - * s.unicode_normalized? # Raises Encoding::CompatibilityError. - * - * Related: String#unicode_normalize, String#unicode_normalize!. + * 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) @@ -12446,9 +12234,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 @@ -12606,18 +12394,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#<=>. */ @@ -12667,9 +12461,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#=~. * @@ -12715,11 +12509,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#[]. * @@ -12760,7 +12554,7 @@ sym_empty(VALUE sym) /* * call-seq: - * upcase(*options) -> symbol + * upcase(mapping) -> symbol * * Equivalent to <tt>sym.to_s.upcase.to_sym</tt>. * @@ -12776,7 +12570,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>. * @@ -12794,7 +12588,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>. * @@ -12810,7 +12604,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>. * @@ -12927,8 +12721,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, false); + 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 @@ -12944,7 +12746,7 @@ rb_enc_interned_str(const char *ptr, long len, rb_encoding *enc) rb_enc_autoload(enc); } - struct RString fake_str; + struct RString fake_str = {RBASIC_INIT}; return register_fstring(rb_setup_fake_str(&fake_str, ptr, len, enc), true, false); } @@ -12955,8 +12757,10 @@ rb_enc_literal_str(const char *ptr, long len, rb_encoding *enc) rb_enc_autoload(enc); } - struct RString fake_str; - return register_fstring(rb_setup_fake_str(&fake_str, ptr, len, enc), true, 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 @@ -12965,9 +12769,9 @@ rb_enc_interned_str_cstr(const char *ptr, rb_encoding *enc) return rb_enc_interned_str(ptr, strlen(ptr), enc); } -#if USE_YJIT +#if USE_YJIT || USE_ZJIT void -rb_yjit_str_concat_codepoint(VALUE str, VALUE codepoint) +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); @@ -12982,21 +12786,27 @@ rb_yjit_str_concat_codepoint(VALUE str, VALUE 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); - struct fstring_table_struct *fstring_table = RTYPEDDATA_GET_DATA(fstring_table_obj); - for (unsigned int i = 0; i < fstring_table->capacity; i++) { - VALUE str = fstring_table->entries[i].str; - if (!str) continue; - RBASIC_SET_CLASS(str, 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); @@ -13027,7 +12837,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); @@ -13098,9 +12907,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); @@ -13108,9 +12917,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); @@ -13195,3 +13004,4 @@ Init_String(void) rb_define_method(rb_cSymbol, "encoding", sym_encoding, 0); } + |
