summaryrefslogtreecommitdiff
path: root/string.c
diff options
context:
space:
mode:
Diffstat (limited to 'string.c')
-rw-r--r--string.c2674
1 files changed, 1242 insertions, 1432 deletions
diff --git a/string.c b/string.c
index 696a24b7fc..464eab2146 100644
--- a/string.c
+++ b/string.c
@@ -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);
}
+