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