mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
cfd162d535
The documentation already specifies that they strip whitespace and defines whitespace to include null. This wraps the new behavior in the appropriate guards in the specs, but does not specify behavior for previous versions, because this is a bug that could be backported. Fixes [Bug #17467]
11978 lines
315 KiB
C
11978 lines
315 KiB
C
/**********************************************************************
|
|
|
|
string.c -
|
|
|
|
$Author$
|
|
created at: Mon Aug 9 17:12:58 JST 1993
|
|
|
|
Copyright (C) 1993-2007 Yukihiro Matsumoto
|
|
Copyright (C) 2000 Network Applied Communication Laboratory, Inc.
|
|
Copyright (C) 2000 Information-technology Promotion Agency, Japan
|
|
|
|
**********************************************************************/
|
|
|
|
#include "ruby/internal/config.h"
|
|
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <math.h>
|
|
|
|
#ifdef HAVE_UNISTD_H
|
|
# include <unistd.h>
|
|
#endif
|
|
|
|
#if defined HAVE_CRYPT_R
|
|
# if defined HAVE_CRYPT_H
|
|
# include <crypt.h>
|
|
# endif
|
|
#elif !defined HAVE_CRYPT
|
|
# include "missing/crypt.h"
|
|
# define HAVE_CRYPT_R 1
|
|
#endif
|
|
|
|
#include "debug_counter.h"
|
|
#include "encindex.h"
|
|
#include "gc.h"
|
|
#include "id.h"
|
|
#include "internal.h"
|
|
#include "internal/array.h"
|
|
#include "internal/compar.h"
|
|
#include "internal/compilers.h"
|
|
#include "internal/encoding.h"
|
|
#include "internal/error.h"
|
|
#include "internal/gc.h"
|
|
#include "internal/numeric.h"
|
|
#include "internal/object.h"
|
|
#include "internal/proc.h"
|
|
#include "internal/re.h"
|
|
#include "internal/sanitizers.h"
|
|
#include "internal/string.h"
|
|
#include "internal/transcode.h"
|
|
#include "probes.h"
|
|
#include "ruby/encoding.h"
|
|
#include "ruby/re.h"
|
|
#include "ruby/util.h"
|
|
#include "ruby_assert.h"
|
|
#include "vm_sync.h"
|
|
|
|
#define BEG(no) (regs->beg[(no)])
|
|
#define END(no) (regs->end[(no)])
|
|
|
|
#undef rb_str_new
|
|
#undef rb_usascii_str_new
|
|
#undef rb_utf8_str_new
|
|
#undef rb_enc_str_new
|
|
#undef rb_str_new_cstr
|
|
#undef rb_tainted_str_new_cstr
|
|
#undef rb_usascii_str_new_cstr
|
|
#undef rb_utf8_str_new_cstr
|
|
#undef rb_enc_str_new_cstr
|
|
#undef rb_external_str_new_cstr
|
|
#undef rb_locale_str_new_cstr
|
|
#undef rb_str_dup_frozen
|
|
#undef rb_str_buf_new_cstr
|
|
#undef rb_str_buf_cat
|
|
#undef rb_str_buf_cat2
|
|
#undef rb_str_cat2
|
|
#undef rb_str_cat_cstr
|
|
#undef rb_fstring_cstr
|
|
|
|
VALUE rb_cString;
|
|
VALUE rb_cSymbol;
|
|
|
|
/* FLAGS of RString
|
|
*
|
|
* 1: RSTRING_NOEMBED
|
|
* 2: STR_SHARED (== ELTS_SHARED)
|
|
* 2-6: RSTRING_EMBED_LEN (5 bits == 32)
|
|
* 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
|
|
* 8-9: ENC_CODERANGE (2 bits)
|
|
* 10-16: ENCODING (7 bits == 128)
|
|
* 17: RSTRING_FSTR
|
|
* 18: STR_NOFREE
|
|
* 19: STR_FAKESTR
|
|
*/
|
|
|
|
#define RUBY_MAX_CHAR_LEN 16
|
|
#define STR_SHARED_ROOT FL_USER5
|
|
#define STR_BORROWED FL_USER6
|
|
#define STR_TMPLOCK FL_USER7
|
|
#define STR_NOFREE FL_USER18
|
|
#define STR_FAKESTR FL_USER19
|
|
|
|
#define STR_SET_NOEMBED(str) do {\
|
|
FL_SET((str), STR_NOEMBED);\
|
|
STR_SET_EMBED_LEN((str), 0);\
|
|
} while (0)
|
|
#define STR_SET_EMBED(str) FL_UNSET((str), (STR_NOEMBED|STR_NOFREE))
|
|
#define STR_SET_EMBED_LEN(str, n) do { \
|
|
long tmp_n = (n);\
|
|
RBASIC(str)->flags &= ~RSTRING_EMBED_LEN_MASK;\
|
|
RBASIC(str)->flags |= (tmp_n) << RSTRING_EMBED_LEN_SHIFT;\
|
|
} while (0)
|
|
|
|
#define STR_SET_LEN(str, n) do { \
|
|
if (STR_EMBED_P(str)) {\
|
|
STR_SET_EMBED_LEN((str), (n));\
|
|
}\
|
|
else {\
|
|
RSTRING(str)->as.heap.len = (n);\
|
|
}\
|
|
} while (0)
|
|
|
|
#define STR_DEC_LEN(str) do {\
|
|
if (STR_EMBED_P(str)) {\
|
|
long n = RSTRING_LEN(str);\
|
|
n--;\
|
|
STR_SET_EMBED_LEN((str), n);\
|
|
}\
|
|
else {\
|
|
RSTRING(str)->as.heap.len--;\
|
|
}\
|
|
} while (0)
|
|
|
|
#define TERM_LEN(str) rb_enc_mbminlen(rb_enc_get(str))
|
|
#define TERM_FILL(ptr, termlen) do {\
|
|
char *const term_fill_ptr = (ptr);\
|
|
const int term_fill_len = (termlen);\
|
|
*term_fill_ptr = '\0';\
|
|
if (UNLIKELY(term_fill_len > 1))\
|
|
memset(term_fill_ptr, 0, term_fill_len);\
|
|
} while (0)
|
|
|
|
#define RESIZE_CAPA(str,capacity) do {\
|
|
const int termlen = TERM_LEN(str);\
|
|
RESIZE_CAPA_TERM(str,capacity,termlen);\
|
|
} while (0)
|
|
#define RESIZE_CAPA_TERM(str,capacity,termlen) do {\
|
|
if (STR_EMBED_P(str)) {\
|
|
if (!STR_EMBEDDABLE_P(capacity, termlen)) {\
|
|
char *const tmp = ALLOC_N(char, (size_t)(capacity) + (termlen));\
|
|
const long tlen = RSTRING_LEN(str);\
|
|
memcpy(tmp, RSTRING_PTR(str), tlen);\
|
|
RSTRING(str)->as.heap.ptr = tmp;\
|
|
RSTRING(str)->as.heap.len = tlen;\
|
|
STR_SET_NOEMBED(str);\
|
|
RSTRING(str)->as.heap.aux.capa = (capacity);\
|
|
}\
|
|
}\
|
|
else {\
|
|
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);\
|
|
}\
|
|
} while (0)
|
|
|
|
#define STR_SET_SHARED(str, shared_str) do { \
|
|
if (!FL_TEST(str, STR_FAKESTR)) { \
|
|
RB_OBJ_WRITE((str), &RSTRING(str)->as.heap.aux.shared, (shared_str)); \
|
|
FL_SET((str), STR_SHARED); \
|
|
FL_SET((shared_str), STR_SHARED_ROOT); \
|
|
if (RBASIC_CLASS((shared_str)) == 0) /* for CoW-friendliness */ \
|
|
FL_SET_RAW((shared_str), STR_BORROWED); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define STR_HEAP_PTR(str) (RSTRING(str)->as.heap.ptr)
|
|
#define STR_HEAP_SIZE(str) ((size_t)RSTRING(str)->as.heap.aux.capa + TERM_LEN(str))
|
|
/* TODO: include the terminator size in capa. */
|
|
|
|
#define STR_ENC_GET(str) get_encoding(str)
|
|
|
|
#if !defined SHARABLE_MIDDLE_SUBSTRING
|
|
# define SHARABLE_MIDDLE_SUBSTRING 0
|
|
#endif
|
|
#if !SHARABLE_MIDDLE_SUBSTRING
|
|
#define SHARABLE_SUBSTRING_P(beg, len, end) ((beg) + (len) == (end))
|
|
#else
|
|
#define SHARABLE_SUBSTRING_P(beg, len, end) 1
|
|
#endif
|
|
|
|
#define STR_EMBEDDABLE_P(len, termlen) \
|
|
((len) <= RSTRING_EMBED_LEN_MAX + 1 - (termlen))
|
|
|
|
static VALUE str_replace_shared_without_enc(VALUE str2, VALUE str);
|
|
static VALUE str_new_frozen(VALUE klass, VALUE orig);
|
|
static VALUE str_new_frozen_buffer(VALUE klass, VALUE orig, int copy_encoding);
|
|
static VALUE str_new_static(VALUE klass, const char *ptr, long len, int encindex);
|
|
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 void
|
|
str_make_independent(VALUE str)
|
|
{
|
|
long len = RSTRING_LEN(str);
|
|
int termlen = TERM_LEN(str);
|
|
str_make_independent_expand((str), len, 0L, termlen);
|
|
}
|
|
|
|
static inline int str_dependent_p(VALUE str);
|
|
|
|
void
|
|
rb_str_make_independent(VALUE str)
|
|
{
|
|
if (str_dependent_p(str)) {
|
|
str_make_independent(str);
|
|
}
|
|
}
|
|
|
|
/* symbols for [up|down|swap]case/capitalize options */
|
|
static VALUE sym_ascii, sym_turkic, sym_lithuanian, sym_fold;
|
|
|
|
static rb_encoding *
|
|
get_actual_encoding(const int encidx, VALUE str)
|
|
{
|
|
const unsigned char *q;
|
|
|
|
switch (encidx) {
|
|
case ENCINDEX_UTF_16:
|
|
if (RSTRING_LEN(str) < 2) break;
|
|
q = (const unsigned char *)RSTRING_PTR(str);
|
|
if (q[0] == 0xFE && q[1] == 0xFF) {
|
|
return rb_enc_get_from_index(ENCINDEX_UTF_16BE);
|
|
}
|
|
if (q[0] == 0xFF && q[1] == 0xFE) {
|
|
return rb_enc_get_from_index(ENCINDEX_UTF_16LE);
|
|
}
|
|
return rb_ascii8bit_encoding();
|
|
case ENCINDEX_UTF_32:
|
|
if (RSTRING_LEN(str) < 4) break;
|
|
q = (const unsigned char *)RSTRING_PTR(str);
|
|
if (q[0] == 0 && q[1] == 0 && q[2] == 0xFE && q[3] == 0xFF) {
|
|
return rb_enc_get_from_index(ENCINDEX_UTF_32BE);
|
|
}
|
|
if (q[3] == 0 && q[2] == 0 && q[1] == 0xFE && q[0] == 0xFF) {
|
|
return rb_enc_get_from_index(ENCINDEX_UTF_32LE);
|
|
}
|
|
return rb_ascii8bit_encoding();
|
|
}
|
|
return rb_enc_from_index(encidx);
|
|
}
|
|
|
|
static rb_encoding *
|
|
get_encoding(VALUE str)
|
|
{
|
|
return get_actual_encoding(ENCODING_GET(str), str);
|
|
}
|
|
|
|
static void
|
|
mustnot_broken(VALUE str)
|
|
{
|
|
if (is_broken_string(str)) {
|
|
rb_raise(rb_eArgError, "invalid byte sequence in %s", rb_enc_name(STR_ENC_GET(str)));
|
|
}
|
|
}
|
|
|
|
static void
|
|
mustnot_wchar(VALUE str)
|
|
{
|
|
rb_encoding *enc = STR_ENC_GET(str);
|
|
if (rb_enc_mbminlen(enc) > 1) {
|
|
rb_raise(rb_eArgError, "wide char encoding: %s", rb_enc_name(enc));
|
|
}
|
|
}
|
|
|
|
static int fstring_cmp(VALUE a, VALUE b);
|
|
|
|
static VALUE register_fstring(VALUE str, bool copy);
|
|
|
|
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)
|
|
|
|
struct fstr_update_arg {
|
|
VALUE fstr;
|
|
bool copy;
|
|
};
|
|
|
|
static int
|
|
fstr_update_callback(st_data_t *key, st_data_t *value, st_data_t data, int existing)
|
|
{
|
|
|
|
struct fstr_update_arg *arg = (struct fstr_update_arg *)data;
|
|
VALUE str = (VALUE)*key;
|
|
|
|
if (existing) {
|
|
/* because of lazy sweep, str may be unmarked already and swept
|
|
* at next time */
|
|
|
|
if (rb_objspace_garbage_object_p(str)) {
|
|
arg->fstr = Qundef;
|
|
return ST_DELETE;
|
|
}
|
|
|
|
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)->as.heap.len);
|
|
rb_enc_copy(new_str, str);
|
|
str = new_str;
|
|
}
|
|
else {
|
|
str = str_new_static(rb_cString, RSTRING(str)->as.heap.ptr,
|
|
RSTRING(str)->as.heap.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;
|
|
|
|
*key = *value = arg->fstr = str;
|
|
return ST_CONTINUE;
|
|
}
|
|
}
|
|
|
|
RUBY_FUNC_EXPORTED
|
|
VALUE
|
|
rb_fstring(VALUE str)
|
|
{
|
|
VALUE fstr;
|
|
int bare;
|
|
|
|
Check_Type(str, T_STRING);
|
|
|
|
if (FL_TEST(str, RSTRING_FSTR))
|
|
return str;
|
|
|
|
bare = BARE_STRING_P(str);
|
|
if (!bare) {
|
|
if (STR_EMBED_P(str)) {
|
|
OBJ_FREEZE_RAW(str);
|
|
return str;
|
|
}
|
|
if (FL_TEST_RAW(str, STR_NOEMBED|STR_SHARED_ROOT|STR_SHARED) == (STR_NOEMBED|STR_SHARED_ROOT)) {
|
|
assert(OBJ_FROZEN(str));
|
|
return str;
|
|
}
|
|
}
|
|
|
|
if (!OBJ_FROZEN(str))
|
|
rb_str_resize(str, RSTRING_LEN(str));
|
|
|
|
fstr = register_fstring(str, FALSE);
|
|
|
|
if (!bare) {
|
|
str_replace_shared_without_enc(str, fstr);
|
|
OBJ_FREEZE_RAW(str);
|
|
return str;
|
|
}
|
|
return fstr;
|
|
}
|
|
|
|
static VALUE
|
|
register_fstring(VALUE str, bool copy)
|
|
{
|
|
struct fstr_update_arg args;
|
|
args.copy = copy;
|
|
|
|
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 (args.fstr == Qundef);
|
|
}
|
|
RB_VM_LOCK_LEAVE();
|
|
|
|
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;
|
|
}
|
|
|
|
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 */
|
|
|
|
ENCODING_SET_INLINED((VALUE)fake_str, encidx);
|
|
|
|
RBASIC_SET_CLASS_RAW((VALUE)fake_str, rb_cString);
|
|
fake_str->as.heap.len = len;
|
|
fake_str->as.heap.ptr = (char *)name;
|
|
fake_str->as.heap.aux.capa = len;
|
|
return (VALUE)fake_str;
|
|
}
|
|
|
|
/*
|
|
* set up a fake string which refers a static string literal.
|
|
*/
|
|
VALUE
|
|
rb_setup_fake_str(struct RString *fake_str, const char *name, long len, rb_encoding *enc)
|
|
{
|
|
return setup_fake_str(fake_str, name, len, rb_enc_to_index(enc));
|
|
}
|
|
|
|
/*
|
|
* rb_fstring_new and rb_fstring_cstr family create or lookup a frozen
|
|
* shared string which refers a static string literal. `ptr` must
|
|
* point a constant string.
|
|
*/
|
|
MJIT_FUNC_EXPORTED 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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
VALUE
|
|
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
|
|
single_byte_optimizable(VALUE str)
|
|
{
|
|
rb_encoding *enc;
|
|
|
|
/* Conservative. It may be ENC_CODERANGE_UNKNOWN. */
|
|
if (ENC_CODERANGE(str) == ENC_CODERANGE_7BIT)
|
|
return 1;
|
|
|
|
enc = STR_ENC_GET(str);
|
|
if (rb_enc_mbmaxlen(enc) == 1)
|
|
return 1;
|
|
|
|
/* Conservative. Possibly single byte.
|
|
* "\xa1" in Shift_JIS for example. */
|
|
return 0;
|
|
}
|
|
|
|
VALUE rb_fs;
|
|
|
|
static inline const char *
|
|
search_nonascii(const char *p, const char *e)
|
|
{
|
|
const uintptr_t *s, *t;
|
|
|
|
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
|
|
# if SIZEOF_UINTPTR_T == 8
|
|
# define NONASCII_MASK UINT64_C(0x8080808080808080)
|
|
# elif SIZEOF_UINTPTR_T == 4
|
|
# define NONASCII_MASK UINT32_C(0x80808080)
|
|
# else
|
|
# error "don't know what to do."
|
|
# endif
|
|
#else
|
|
# if SIZEOF_UINTPTR_T == 8
|
|
# define NONASCII_MASK ((uintptr_t)0x80808080UL << 32 | (uintptr_t)0x80808080UL)
|
|
# elif SIZEOF_UINTPTR_T == 4
|
|
# define NONASCII_MASK 0x80808080UL /* or...? */
|
|
# else
|
|
# error "don't know what to do."
|
|
# endif
|
|
#endif
|
|
|
|
if (UNALIGNED_WORD_ACCESS || e - p >= SIZEOF_VOIDP) {
|
|
#if !UNALIGNED_WORD_ACCESS
|
|
if ((uintptr_t)p % SIZEOF_VOIDP) {
|
|
int l = SIZEOF_VOIDP - (uintptr_t)p % SIZEOF_VOIDP;
|
|
p += l;
|
|
switch (l) {
|
|
default: UNREACHABLE;
|
|
#if SIZEOF_VOIDP > 4
|
|
case 7: if (p[-7]&0x80) return p-7;
|
|
case 6: if (p[-6]&0x80) return p-6;
|
|
case 5: if (p[-5]&0x80) return p-5;
|
|
case 4: if (p[-4]&0x80) return p-4;
|
|
#endif
|
|
case 3: if (p[-3]&0x80) return p-3;
|
|
case 2: if (p[-2]&0x80) return p-2;
|
|
case 1: if (p[-1]&0x80) return p-1;
|
|
case 0: break;
|
|
}
|
|
}
|
|
#endif
|
|
#if defined(HAVE_BUILTIN___BUILTIN_ASSUME_ALIGNED) &&! UNALIGNED_WORD_ACCESS
|
|
#define aligned_ptr(value) \
|
|
__builtin_assume_aligned((value), sizeof(uintptr_t))
|
|
#else
|
|
#define aligned_ptr(value) (uintptr_t *)(value)
|
|
#endif
|
|
s = aligned_ptr(p);
|
|
t = (uintptr_t *)(e - (SIZEOF_VOIDP-1));
|
|
#undef aligned_ptr
|
|
for (;s < t; s++) {
|
|
if (*s & NONASCII_MASK) {
|
|
#ifdef WORDS_BIGENDIAN
|
|
return (const char *)s + (nlz_intptr(*s&NONASCII_MASK)>>3);
|
|
#else
|
|
return (const char *)s + (ntz_intptr(*s&NONASCII_MASK)>>3);
|
|
#endif
|
|
}
|
|
}
|
|
p = (const char *)s;
|
|
}
|
|
|
|
switch (e - p) {
|
|
default: UNREACHABLE;
|
|
#if SIZEOF_VOIDP > 4
|
|
case 7: if (e[-7]&0x80) return e-7;
|
|
case 6: if (e[-6]&0x80) return e-6;
|
|
case 5: if (e[-5]&0x80) return e-5;
|
|
case 4: if (e[-4]&0x80) return e-4;
|
|
#endif
|
|
case 3: if (e[-3]&0x80) return e-3;
|
|
case 2: if (e[-2]&0x80) return e-2;
|
|
case 1: if (e[-1]&0x80) return e-1;
|
|
case 0: return NULL;
|
|
}
|
|
}
|
|
|
|
static int
|
|
coderange_scan(const char *p, long len, rb_encoding *enc)
|
|
{
|
|
const char *e = p + len;
|
|
|
|
if (rb_enc_to_index(enc) == rb_ascii8bit_encindex()) {
|
|
/* enc is ASCII-8BIT. ASCII-8BIT string never be broken. */
|
|
p = search_nonascii(p, e);
|
|
return p ? ENC_CODERANGE_VALID : ENC_CODERANGE_7BIT;
|
|
}
|
|
|
|
if (rb_enc_asciicompat(enc)) {
|
|
p = search_nonascii(p, e);
|
|
if (!p) return ENC_CODERANGE_7BIT;
|
|
for (;;) {
|
|
int ret = rb_enc_precise_mbclen(p, e, enc);
|
|
if (!MBCLEN_CHARFOUND_P(ret)) return ENC_CODERANGE_BROKEN;
|
|
p += MBCLEN_CHARFOUND_LEN(ret);
|
|
if (p == e) break;
|
|
p = search_nonascii(p, e);
|
|
if (!p) break;
|
|
}
|
|
}
|
|
else {
|
|
while (p < e) {
|
|
int ret = rb_enc_precise_mbclen(p, e, enc);
|
|
if (!MBCLEN_CHARFOUND_P(ret)) return ENC_CODERANGE_BROKEN;
|
|
p += MBCLEN_CHARFOUND_LEN(ret);
|
|
}
|
|
}
|
|
return ENC_CODERANGE_VALID;
|
|
}
|
|
|
|
long
|
|
rb_str_coderange_scan_restartable(const char *s, const char *e, rb_encoding *enc, int *cr)
|
|
{
|
|
const char *p = s;
|
|
|
|
if (*cr == ENC_CODERANGE_BROKEN)
|
|
return e - s;
|
|
|
|
if (rb_enc_to_index(enc) == rb_ascii8bit_encindex()) {
|
|
/* enc is ASCII-8BIT. ASCII-8BIT string never be broken. */
|
|
if (*cr == ENC_CODERANGE_VALID) return e - s;
|
|
p = search_nonascii(p, e);
|
|
*cr = p ? ENC_CODERANGE_VALID : ENC_CODERANGE_7BIT;
|
|
return e - s;
|
|
}
|
|
else if (rb_enc_asciicompat(enc)) {
|
|
p = search_nonascii(p, e);
|
|
if (!p) {
|
|
if (*cr != ENC_CODERANGE_VALID) *cr = ENC_CODERANGE_7BIT;
|
|
return e - s;
|
|
}
|
|
for (;;) {
|
|
int ret = rb_enc_precise_mbclen(p, e, enc);
|
|
if (!MBCLEN_CHARFOUND_P(ret)) {
|
|
*cr = MBCLEN_INVALID_P(ret) ? ENC_CODERANGE_BROKEN: ENC_CODERANGE_UNKNOWN;
|
|
return p - s;
|
|
}
|
|
p += MBCLEN_CHARFOUND_LEN(ret);
|
|
if (p == e) break;
|
|
p = search_nonascii(p, e);
|
|
if (!p) break;
|
|
}
|
|
}
|
|
else {
|
|
while (p < e) {
|
|
int ret = rb_enc_precise_mbclen(p, e, enc);
|
|
if (!MBCLEN_CHARFOUND_P(ret)) {
|
|
*cr = MBCLEN_INVALID_P(ret) ? ENC_CODERANGE_BROKEN: ENC_CODERANGE_UNKNOWN;
|
|
return p - s;
|
|
}
|
|
p += MBCLEN_CHARFOUND_LEN(ret);
|
|
}
|
|
}
|
|
*cr = ENC_CODERANGE_VALID;
|
|
return e - s;
|
|
}
|
|
|
|
static inline void
|
|
str_enc_copy(VALUE str1, VALUE str2)
|
|
{
|
|
rb_enc_set_index(str1, ENCODING_GET(str2));
|
|
}
|
|
|
|
static void
|
|
rb_enc_cr_str_copy_for_substr(VALUE dest, VALUE src)
|
|
{
|
|
/* this function is designed for copying encoding and coderange
|
|
* from src to new string "dest" which is made from the part of src.
|
|
*/
|
|
str_enc_copy(dest, src);
|
|
if (RSTRING_LEN(dest) == 0) {
|
|
if (!rb_enc_asciicompat(STR_ENC_GET(src)))
|
|
ENC_CODERANGE_SET(dest, ENC_CODERANGE_VALID);
|
|
else
|
|
ENC_CODERANGE_SET(dest, ENC_CODERANGE_7BIT);
|
|
return;
|
|
}
|
|
switch (ENC_CODERANGE(src)) {
|
|
case ENC_CODERANGE_7BIT:
|
|
ENC_CODERANGE_SET(dest, ENC_CODERANGE_7BIT);
|
|
break;
|
|
case ENC_CODERANGE_VALID:
|
|
if (!rb_enc_asciicompat(STR_ENC_GET(src)) ||
|
|
search_nonascii(RSTRING_PTR(dest), RSTRING_END(dest)))
|
|
ENC_CODERANGE_SET(dest, ENC_CODERANGE_VALID);
|
|
else
|
|
ENC_CODERANGE_SET(dest, ENC_CODERANGE_7BIT);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
rb_enc_cr_str_exact_copy(VALUE dest, VALUE src)
|
|
{
|
|
str_enc_copy(dest, src);
|
|
ENC_CODERANGE_SET(dest, ENC_CODERANGE(src));
|
|
}
|
|
|
|
int
|
|
rb_enc_str_coderange(VALUE str)
|
|
{
|
|
int cr = ENC_CODERANGE(str);
|
|
|
|
if (cr == ENC_CODERANGE_UNKNOWN) {
|
|
int encidx = ENCODING_GET(str);
|
|
rb_encoding *enc = rb_enc_from_index(encidx);
|
|
if (rb_enc_mbminlen(enc) > 1 && rb_enc_dummy_p(enc) &&
|
|
rb_enc_mbminlen(enc = get_actual_encoding(encidx, str)) == 1) {
|
|
cr = ENC_CODERANGE_BROKEN;
|
|
}
|
|
else {
|
|
cr = coderange_scan(RSTRING_PTR(str), RSTRING_LEN(str),
|
|
enc);
|
|
}
|
|
ENC_CODERANGE_SET(str, cr);
|
|
}
|
|
return cr;
|
|
}
|
|
|
|
int
|
|
rb_enc_str_asciionly_p(VALUE str)
|
|
{
|
|
rb_encoding *enc = STR_ENC_GET(str);
|
|
|
|
if (!rb_enc_asciicompat(enc))
|
|
return FALSE;
|
|
else if (rb_enc_str_coderange(str) == ENC_CODERANGE_7BIT)
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
static inline void
|
|
str_mod_check(VALUE s, const char *p, long len)
|
|
{
|
|
if (RSTRING_PTR(s) != p || RSTRING_LEN(s) != len){
|
|
rb_raise(rb_eRuntimeError, "string modified");
|
|
}
|
|
}
|
|
|
|
static size_t
|
|
str_capacity(VALUE str, const int termlen)
|
|
{
|
|
if (STR_EMBED_P(str)) {
|
|
return (RSTRING_EMBED_LEN_MAX + 1 - termlen);
|
|
}
|
|
else if (FL_TEST(str, STR_SHARED|STR_NOFREE)) {
|
|
return RSTRING(str)->as.heap.len;
|
|
}
|
|
else {
|
|
return RSTRING(str)->as.heap.aux.capa;
|
|
}
|
|
}
|
|
|
|
size_t
|
|
rb_str_capacity(VALUE str)
|
|
{
|
|
return str_capacity(str, TERM_LEN(str));
|
|
}
|
|
|
|
static inline void
|
|
must_not_null(const char *ptr)
|
|
{
|
|
if (!ptr) {
|
|
rb_raise(rb_eArgError, "NULL pointer given");
|
|
}
|
|
}
|
|
|
|
static inline VALUE
|
|
str_alloc(VALUE klass)
|
|
{
|
|
NEWOBJ_OF(str, struct RString, klass, T_STRING | (RGENGC_WB_PROTECTED_STRING ? FL_WB_PROTECTED : 0));
|
|
return (VALUE)str;
|
|
}
|
|
|
|
static inline VALUE
|
|
empty_str_alloc(VALUE klass)
|
|
{
|
|
RUBY_DTRACE_CREATE_HOOK(STRING, 0);
|
|
return str_alloc(klass);
|
|
}
|
|
|
|
static VALUE
|
|
str_new0(VALUE klass, const char *ptr, long len, int termlen)
|
|
{
|
|
VALUE str;
|
|
|
|
if (len < 0) {
|
|
rb_raise(rb_eArgError, "negative string size (or size too big)");
|
|
}
|
|
|
|
RUBY_DTRACE_CREATE_HOOK(STRING, len);
|
|
|
|
str = str_alloc(klass);
|
|
if (!STR_EMBEDDABLE_P(len, termlen)) {
|
|
RSTRING(str)->as.heap.aux.capa = len;
|
|
RSTRING(str)->as.heap.ptr = ALLOC_N(char, (size_t)len + termlen);
|
|
STR_SET_NOEMBED(str);
|
|
}
|
|
else if (len == 0) {
|
|
ENC_CODERANGE_SET(str, ENC_CODERANGE_7BIT);
|
|
}
|
|
if (ptr) {
|
|
memcpy(RSTRING_PTR(str), ptr, len);
|
|
}
|
|
STR_SET_LEN(str, len);
|
|
TERM_FILL(RSTRING_PTR(str) + len, termlen);
|
|
return str;
|
|
}
|
|
|
|
static VALUE
|
|
str_new(VALUE klass, const char *ptr, long len)
|
|
{
|
|
return str_new0(klass, ptr, len, 1);
|
|
}
|
|
|
|
VALUE
|
|
rb_str_new(const char *ptr, long len)
|
|
{
|
|
return str_new(rb_cString, ptr, 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
VALUE
|
|
rb_str_new_cstr(const char *ptr)
|
|
{
|
|
must_not_null(ptr);
|
|
/* rb_str_new_cstr() can take pointer from non-malloc-generated
|
|
* memory regions, and that cannot be detected by the MSAN. Just
|
|
* trust the programmer that the argument passed here is a sane C
|
|
* string. */
|
|
__msan_unpoison_string(ptr);
|
|
return rb_str_new(ptr, strlen(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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
VALUE
|
|
rb_enc_str_new_cstr(const char *ptr, rb_encoding *enc)
|
|
{
|
|
must_not_null(ptr);
|
|
if (rb_enc_mbminlen(enc) != 1) {
|
|
rb_raise(rb_eArgError, "wchar encoding given");
|
|
}
|
|
return rb_enc_str_new(ptr, strlen(ptr), enc);
|
|
}
|
|
|
|
static VALUE
|
|
str_new_static(VALUE klass, const char *ptr, long len, int encindex)
|
|
{
|
|
VALUE str;
|
|
|
|
if (len < 0) {
|
|
rb_raise(rb_eArgError, "negative string size (or size too big)");
|
|
}
|
|
|
|
if (!ptr) {
|
|
rb_encoding *enc = rb_enc_get_from_index(encindex);
|
|
str = str_new0(klass, ptr, len, rb_enc_mbminlen(enc));
|
|
}
|
|
else {
|
|
RUBY_DTRACE_CREATE_HOOK(STRING, len);
|
|
str = str_alloc(klass);
|
|
RSTRING(str)->as.heap.len = len;
|
|
RSTRING(str)->as.heap.ptr = (char *)ptr;
|
|
RSTRING(str)->as.heap.aux.capa = len;
|
|
STR_SET_NOEMBED(str);
|
|
RBASIC(str)->flags |= STR_NOFREE;
|
|
}
|
|
rb_enc_associate_index(str, encindex);
|
|
return str;
|
|
}
|
|
|
|
VALUE
|
|
rb_str_new_static(const char *ptr, long len)
|
|
{
|
|
return str_new_static(rb_cString, ptr, len, 0);
|
|
}
|
|
|
|
VALUE
|
|
rb_usascii_str_new_static(const char *ptr, long len)
|
|
{
|
|
return str_new_static(rb_cString, ptr, len, ENCINDEX_US_ASCII);
|
|
}
|
|
|
|
VALUE
|
|
rb_utf8_str_new_static(const char *ptr, long len)
|
|
{
|
|
return str_new_static(rb_cString, ptr, len, ENCINDEX_UTF_8);
|
|
}
|
|
|
|
VALUE
|
|
rb_enc_str_new_static(const char *ptr, long len, rb_encoding *enc)
|
|
{
|
|
return str_new_static(rb_cString, ptr, len, rb_enc_to_index(enc));
|
|
}
|
|
|
|
VALUE
|
|
rb_tainted_str_new(const char *ptr, long len)
|
|
{
|
|
rb_warn_deprecated_to_remove("rb_tainted_str_new", "3.2");
|
|
return rb_str_new(ptr, len);
|
|
}
|
|
|
|
VALUE
|
|
rb_tainted_str_new_cstr(const char *ptr)
|
|
{
|
|
rb_warn_deprecated_to_remove("rb_tainted_str_new_cstr", "3.2");
|
|
return rb_str_new_cstr(ptr);
|
|
}
|
|
|
|
static VALUE str_cat_conv_enc_opts(VALUE newstr, long ofs, const char *ptr, long len,
|
|
rb_encoding *from, rb_encoding *to,
|
|
int ecflags, VALUE ecopts);
|
|
|
|
VALUE
|
|
rb_str_conv_enc_opts(VALUE str, rb_encoding *from, rb_encoding *to, int ecflags, VALUE ecopts)
|
|
{
|
|
long len;
|
|
const char *ptr;
|
|
VALUE newstr;
|
|
|
|
if (!to) return str;
|
|
if (!from) from = rb_enc_get(str);
|
|
if (from == to) return str;
|
|
if ((rb_enc_asciicompat(to) && is_ascii_string(str)) ||
|
|
to == rb_ascii8bit_encoding()) {
|
|
if (STR_ENC_GET(str) != to) {
|
|
str = rb_str_dup(str);
|
|
rb_enc_associate(str, to);
|
|
}
|
|
return str;
|
|
}
|
|
|
|
RSTRING_GETMEM(str, ptr, len);
|
|
newstr = str_cat_conv_enc_opts(rb_str_buf_new(len), 0, ptr, len,
|
|
from, to, ecflags, ecopts);
|
|
if (NIL_P(newstr)) {
|
|
/* some error, return original */
|
|
return str;
|
|
}
|
|
return newstr;
|
|
}
|
|
|
|
VALUE
|
|
rb_str_cat_conv_enc_opts(VALUE newstr, long ofs, const char *ptr, long len,
|
|
rb_encoding *from, int ecflags, VALUE ecopts)
|
|
{
|
|
long olen;
|
|
|
|
olen = RSTRING_LEN(newstr);
|
|
if (ofs < -olen || olen < ofs)
|
|
rb_raise(rb_eIndexError, "index %ld out of string", ofs);
|
|
if (ofs < 0) ofs += olen;
|
|
if (!from) {
|
|
STR_SET_LEN(newstr, ofs);
|
|
return rb_str_cat(newstr, ptr, len);
|
|
}
|
|
|
|
rb_str_modify(newstr);
|
|
return str_cat_conv_enc_opts(newstr, ofs, ptr, len, from,
|
|
rb_enc_get(newstr),
|
|
ecflags, ecopts);
|
|
}
|
|
|
|
VALUE
|
|
rb_str_initialize(VALUE str, const char *ptr, long len, rb_encoding *enc)
|
|
{
|
|
STR_SET_LEN(str, 0);
|
|
rb_enc_associate(str, enc);
|
|
rb_str_cat(str, ptr, len);
|
|
return str;
|
|
}
|
|
|
|
static VALUE
|
|
str_cat_conv_enc_opts(VALUE newstr, long ofs, const char *ptr, long len,
|
|
rb_encoding *from, rb_encoding *to,
|
|
int ecflags, VALUE ecopts)
|
|
{
|
|
rb_econv_t *ec;
|
|
rb_econv_result_t ret;
|
|
long olen;
|
|
VALUE econv_wrapper;
|
|
const unsigned char *start, *sp;
|
|
unsigned char *dest, *dp;
|
|
size_t converted_output = (size_t)ofs;
|
|
|
|
olen = rb_str_capacity(newstr);
|
|
|
|
econv_wrapper = rb_obj_alloc(rb_cEncodingConverter);
|
|
RBASIC_CLEAR_CLASS(econv_wrapper);
|
|
ec = rb_econv_open_opts(from->name, to->name, ecflags, ecopts);
|
|
if (!ec) return Qnil;
|
|
DATA_PTR(econv_wrapper) = ec;
|
|
|
|
sp = (unsigned char*)ptr;
|
|
start = sp;
|
|
while ((dest = (unsigned char*)RSTRING_PTR(newstr)),
|
|
(dp = dest + converted_output),
|
|
(ret = rb_econv_convert(ec, &sp, start + len, &dp, dest + olen, 0)),
|
|
ret == econv_destination_buffer_full) {
|
|
/* destination buffer short */
|
|
size_t converted_input = sp - start;
|
|
size_t rest = len - converted_input;
|
|
converted_output = dp - dest;
|
|
rb_str_set_len(newstr, converted_output);
|
|
if (converted_input && converted_output &&
|
|
rest < (LONG_MAX / converted_output)) {
|
|
rest = (rest * converted_output) / converted_input;
|
|
}
|
|
else {
|
|
rest = olen;
|
|
}
|
|
olen += rest < 2 ? 2 : rest;
|
|
rb_str_resize(newstr, olen);
|
|
}
|
|
DATA_PTR(econv_wrapper) = 0;
|
|
rb_econv_close(ec);
|
|
rb_gc_force_recycle(econv_wrapper);
|
|
switch (ret) {
|
|
case econv_finished:
|
|
len = dp - (unsigned char*)RSTRING_PTR(newstr);
|
|
rb_str_set_len(newstr, len);
|
|
rb_enc_associate(newstr, to);
|
|
return newstr;
|
|
|
|
default:
|
|
return Qnil;
|
|
}
|
|
}
|
|
|
|
VALUE
|
|
rb_str_conv_enc(VALUE str, rb_encoding *from, rb_encoding *to)
|
|
{
|
|
return rb_str_conv_enc_opts(str, from, to, 0, Qnil);
|
|
}
|
|
|
|
VALUE
|
|
rb_external_str_new_with_enc(const char *ptr, long len, rb_encoding *eenc)
|
|
{
|
|
rb_encoding *ienc;
|
|
VALUE str;
|
|
const int eidx = rb_enc_to_index(eenc);
|
|
|
|
if (!ptr) {
|
|
return rb_enc_str_new(ptr, len, eenc);
|
|
}
|
|
|
|
/* ASCII-8BIT case, no conversion */
|
|
if ((eidx == rb_ascii8bit_encindex()) ||
|
|
(eidx == rb_usascii_encindex() && search_nonascii(ptr, ptr + len))) {
|
|
return rb_str_new(ptr, len);
|
|
}
|
|
/* no default_internal or same encoding, no conversion */
|
|
ienc = rb_default_internal_encoding();
|
|
if (!ienc || eenc == ienc) {
|
|
return rb_enc_str_new(ptr, len, eenc);
|
|
}
|
|
/* ASCII compatible, and ASCII only string, no conversion in
|
|
* default_internal */
|
|
if ((eidx == rb_ascii8bit_encindex()) ||
|
|
(eidx == rb_usascii_encindex()) ||
|
|
(rb_enc_asciicompat(eenc) && !search_nonascii(ptr, ptr + len))) {
|
|
return rb_enc_str_new(ptr, len, ienc);
|
|
}
|
|
/* convert from the given encoding to default_internal */
|
|
str = rb_enc_str_new(NULL, 0, ienc);
|
|
/* when the conversion failed for some reason, just ignore the
|
|
* default_internal and result in the given encoding as-is. */
|
|
if (NIL_P(rb_str_cat_conv_enc_opts(str, 0, ptr, len, eenc, 0, Qnil))) {
|
|
rb_str_initialize(str, ptr, len, eenc);
|
|
}
|
|
return str;
|
|
}
|
|
|
|
VALUE
|
|
rb_external_str_with_enc(VALUE str, rb_encoding *eenc)
|
|
{
|
|
int eidx = rb_enc_to_index(eenc);
|
|
if (eidx == rb_usascii_encindex() &&
|
|
rb_enc_str_coderange(str) != ENC_CODERANGE_7BIT) {
|
|
rb_enc_associate_index(str, rb_ascii8bit_encindex());
|
|
return str;
|
|
}
|
|
rb_enc_associate_index(str, eidx);
|
|
return rb_str_conv_enc(str, eenc, rb_default_internal_encoding());
|
|
}
|
|
|
|
VALUE
|
|
rb_external_str_new(const char *ptr, long len)
|
|
{
|
|
return rb_external_str_new_with_enc(ptr, len, rb_default_external_encoding());
|
|
}
|
|
|
|
VALUE
|
|
rb_external_str_new_cstr(const char *ptr)
|
|
{
|
|
return rb_external_str_new_with_enc(ptr, strlen(ptr), rb_default_external_encoding());
|
|
}
|
|
|
|
VALUE
|
|
rb_locale_str_new(const char *ptr, long len)
|
|
{
|
|
return rb_external_str_new_with_enc(ptr, len, rb_locale_encoding());
|
|
}
|
|
|
|
VALUE
|
|
rb_locale_str_new_cstr(const char *ptr)
|
|
{
|
|
return rb_external_str_new_with_enc(ptr, strlen(ptr), rb_locale_encoding());
|
|
}
|
|
|
|
VALUE
|
|
rb_filesystem_str_new(const char *ptr, long len)
|
|
{
|
|
return rb_external_str_new_with_enc(ptr, len, rb_filesystem_encoding());
|
|
}
|
|
|
|
VALUE
|
|
rb_filesystem_str_new_cstr(const char *ptr)
|
|
{
|
|
return rb_external_str_new_with_enc(ptr, strlen(ptr), rb_filesystem_encoding());
|
|
}
|
|
|
|
VALUE
|
|
rb_str_export(VALUE str)
|
|
{
|
|
return rb_str_conv_enc(str, STR_ENC_GET(str), rb_default_external_encoding());
|
|
}
|
|
|
|
VALUE
|
|
rb_str_export_locale(VALUE str)
|
|
{
|
|
return rb_str_conv_enc(str, STR_ENC_GET(str), rb_locale_encoding());
|
|
}
|
|
|
|
VALUE
|
|
rb_str_export_to_enc(VALUE str, rb_encoding *enc)
|
|
{
|
|
return rb_str_conv_enc(str, STR_ENC_GET(str), enc);
|
|
}
|
|
|
|
static VALUE
|
|
str_replace_shared_without_enc(VALUE str2, VALUE str)
|
|
{
|
|
const int termlen = TERM_LEN(str);
|
|
char *ptr;
|
|
long len;
|
|
|
|
RSTRING_GETMEM(str, ptr, len);
|
|
if (STR_EMBEDDABLE_P(len, termlen)) {
|
|
char *ptr2 = RSTRING(str2)->as.ary;
|
|
STR_SET_EMBED(str2);
|
|
memcpy(ptr2, RSTRING_PTR(str), len);
|
|
STR_SET_EMBED_LEN(str2, len);
|
|
TERM_FILL(ptr2+len, termlen);
|
|
}
|
|
else {
|
|
VALUE root;
|
|
if (STR_SHARED_P(str)) {
|
|
root = RSTRING(str)->as.heap.aux.shared;
|
|
RSTRING_GETMEM(str, ptr, len);
|
|
}
|
|
else {
|
|
root = rb_str_new_frozen(str);
|
|
RSTRING_GETMEM(root, ptr, len);
|
|
}
|
|
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");
|
|
}
|
|
char *ptr2 = STR_HEAP_PTR(str2);
|
|
if (ptr2 != ptr) {
|
|
ruby_sized_xfree(ptr2, STR_HEAP_SIZE(str2));
|
|
}
|
|
}
|
|
FL_SET(str2, STR_NOEMBED);
|
|
RSTRING(str2)->as.heap.len = len;
|
|
RSTRING(str2)->as.heap.ptr = ptr;
|
|
STR_SET_SHARED(str2, root);
|
|
}
|
|
return str2;
|
|
}
|
|
|
|
static VALUE
|
|
str_replace_shared(VALUE str2, VALUE str)
|
|
{
|
|
str_replace_shared_without_enc(str2, str);
|
|
rb_enc_cr_str_exact_copy(str2, str);
|
|
return str2;
|
|
}
|
|
|
|
static VALUE
|
|
str_new_shared(VALUE klass, VALUE str)
|
|
{
|
|
return str_replace_shared(str_alloc(klass), str);
|
|
}
|
|
|
|
VALUE
|
|
rb_str_new_shared(VALUE str)
|
|
{
|
|
return str_new_shared(rb_obj_class(str), str);
|
|
}
|
|
|
|
VALUE
|
|
rb_str_new_frozen(VALUE orig)
|
|
{
|
|
if (OBJ_FROZEN(orig)) return orig;
|
|
return str_new_frozen(rb_obj_class(orig), orig);
|
|
}
|
|
|
|
static VALUE
|
|
rb_str_new_frozen_String(VALUE orig)
|
|
{
|
|
if (OBJ_FROZEN(orig) && rb_obj_class(orig) == rb_cString) return orig;
|
|
return str_new_frozen(rb_cString, orig);
|
|
}
|
|
|
|
VALUE
|
|
rb_str_tmp_frozen_acquire(VALUE orig)
|
|
{
|
|
if (OBJ_FROZEN_RAW(orig)) return orig;
|
|
return str_new_frozen_buffer(0, orig, FALSE);
|
|
}
|
|
|
|
void
|
|
rb_str_tmp_frozen_release(VALUE orig, VALUE tmp)
|
|
{
|
|
if (RBASIC_CLASS(tmp) != 0)
|
|
return;
|
|
|
|
if (STR_EMBED_P(tmp)) {
|
|
assert(OBJ_FROZEN_RAW(tmp));
|
|
rb_gc_force_recycle(tmp);
|
|
}
|
|
else if (FL_TEST_RAW(orig, STR_SHARED) &&
|
|
!FL_TEST_RAW(orig, STR_TMPLOCK|RUBY_FL_FREEZE)) {
|
|
VALUE shared = RSTRING(orig)->as.heap.aux.shared;
|
|
|
|
if (shared == tmp && !FL_TEST_RAW(tmp, STR_BORROWED)) {
|
|
FL_UNSET_RAW(orig, STR_SHARED);
|
|
assert(RSTRING(orig)->as.heap.ptr == RSTRING(tmp)->as.heap.ptr);
|
|
assert(RSTRING(orig)->as.heap.len == RSTRING(tmp)->as.heap.len);
|
|
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));
|
|
rb_gc_force_recycle(tmp);
|
|
}
|
|
}
|
|
}
|
|
|
|
static VALUE
|
|
str_new_frozen(VALUE klass, VALUE orig)
|
|
{
|
|
return str_new_frozen_buffer(klass, orig, TRUE);
|
|
}
|
|
|
|
static VALUE
|
|
str_new_frozen_buffer(VALUE klass, VALUE orig, int copy_encoding)
|
|
{
|
|
VALUE str;
|
|
|
|
if (STR_EMBED_P(orig)) {
|
|
str = str_new(klass, RSTRING_PTR(orig), RSTRING_LEN(orig));
|
|
}
|
|
else {
|
|
if (FL_TEST_RAW(orig, STR_SHARED)) {
|
|
VALUE shared = RSTRING(orig)->as.heap.aux.shared;
|
|
long ofs = RSTRING(orig)->as.heap.ptr - RSTRING(shared)->as.heap.ptr;
|
|
long rest = RSTRING(shared)->as.heap.len - ofs - RSTRING(orig)->as.heap.len;
|
|
assert(!STR_EMBED_P(shared));
|
|
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);
|
|
RSTRING(str)->as.heap.ptr += ofs;
|
|
RSTRING(str)->as.heap.len -= ofs + rest;
|
|
}
|
|
else {
|
|
if (RBASIC_CLASS(shared) == 0)
|
|
FL_SET_RAW(shared, STR_BORROWED);
|
|
return shared;
|
|
}
|
|
}
|
|
else if (STR_EMBEDDABLE_P(RSTRING_LEN(orig), TERM_LEN(orig))) {
|
|
str = str_alloc(klass);
|
|
STR_SET_EMBED(str);
|
|
memcpy(RSTRING_PTR(str), RSTRING_PTR(orig), RSTRING_LEN(orig));
|
|
STR_SET_EMBED_LEN(str, RSTRING_LEN(orig));
|
|
TERM_FILL(RSTRING_END(str), TERM_LEN(orig));
|
|
}
|
|
else {
|
|
str = str_alloc(klass);
|
|
STR_SET_NOEMBED(str);
|
|
RSTRING(str)->as.heap.len = RSTRING_LEN(orig);
|
|
RSTRING(str)->as.heap.ptr = RSTRING_PTR(orig);
|
|
RSTRING(str)->as.heap.aux.capa = RSTRING(orig)->as.heap.aux.capa;
|
|
RBASIC(str)->flags |= RBASIC(orig)->flags & STR_NOFREE;
|
|
RBASIC(orig)->flags &= ~STR_NOFREE;
|
|
STR_SET_SHARED(orig, str);
|
|
if (klass == 0)
|
|
FL_UNSET_RAW(str, STR_BORROWED);
|
|
}
|
|
}
|
|
|
|
if (copy_encoding) rb_enc_cr_str_exact_copy(str, orig);
|
|
OBJ_FREEZE(str);
|
|
return str;
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
static VALUE
|
|
str_new_empty_String(VALUE str)
|
|
{
|
|
VALUE v = rb_str_new(0, 0);
|
|
rb_enc_copy(v, str);
|
|
return v;
|
|
}
|
|
|
|
#define STR_BUF_MIN_SIZE 63
|
|
STATIC_ASSERT(STR_BUF_MIN_SIZE, STR_BUF_MIN_SIZE > RSTRING_EMBED_LEN_MAX);
|
|
|
|
VALUE
|
|
rb_str_buf_new(long capa)
|
|
{
|
|
VALUE str = str_alloc(rb_cString);
|
|
|
|
if (capa <= RSTRING_EMBED_LEN_MAX) return str;
|
|
if (capa < STR_BUF_MIN_SIZE) {
|
|
capa = STR_BUF_MIN_SIZE;
|
|
}
|
|
FL_SET(str, STR_NOEMBED);
|
|
RSTRING(str)->as.heap.aux.capa = capa;
|
|
RSTRING(str)->as.heap.ptr = ALLOC_N(char, (size_t)capa + 1);
|
|
RSTRING(str)->as.heap.ptr[0] = '\0';
|
|
|
|
return str;
|
|
}
|
|
|
|
VALUE
|
|
rb_str_buf_new_cstr(const char *ptr)
|
|
{
|
|
VALUE str;
|
|
long len = strlen(ptr);
|
|
|
|
str = rb_str_buf_new(len);
|
|
rb_str_buf_cat(str, ptr, len);
|
|
|
|
return str;
|
|
}
|
|
|
|
VALUE
|
|
rb_str_tmp_new(long len)
|
|
{
|
|
return str_new(0, 0, 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);
|
|
}
|
|
else if (FL_TEST(str, STR_SHARED | STR_NOFREE)) {
|
|
(void)RB_DEBUG_COUNTER_INC_IF(obj_str_shared, FL_TEST(str, STR_SHARED));
|
|
(void)RB_DEBUG_COUNTER_INC_IF(obj_str_shared, FL_TEST(str, STR_NOFREE));
|
|
}
|
|
else {
|
|
RB_DEBUG_COUNTER_INC(obj_str_ptr);
|
|
ruby_sized_xfree(STR_HEAP_PTR(str), STR_HEAP_SIZE(str));
|
|
}
|
|
}
|
|
|
|
RUBY_FUNC_EXPORTED size_t
|
|
rb_str_memsize(VALUE str)
|
|
{
|
|
if (FL_TEST(str, STR_NOEMBED|STR_SHARED|STR_NOFREE) == STR_NOEMBED) {
|
|
return STR_HEAP_SIZE(str);
|
|
}
|
|
else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
VALUE
|
|
rb_str_to_str(VALUE str)
|
|
{
|
|
return rb_convert_type_with_id(str, T_STRING, "String", idTo_str);
|
|
}
|
|
|
|
static inline void str_discard(VALUE str);
|
|
static void str_shared_replace(VALUE str, VALUE str2);
|
|
|
|
void
|
|
rb_str_shared_replace(VALUE str, VALUE str2)
|
|
{
|
|
if (str != str2) str_shared_replace(str, str2);
|
|
}
|
|
|
|
static void
|
|
str_shared_replace(VALUE str, VALUE str2)
|
|
{
|
|
rb_encoding *enc;
|
|
int cr;
|
|
int termlen;
|
|
|
|
RUBY_ASSERT(str2 != str);
|
|
enc = STR_ENC_GET(str2);
|
|
cr = ENC_CODERANGE(str2);
|
|
str_discard(str);
|
|
termlen = rb_enc_mbminlen(enc);
|
|
|
|
if (STR_EMBEDDABLE_P(RSTRING_LEN(str2), termlen)) {
|
|
STR_SET_EMBED(str);
|
|
memcpy(RSTRING_PTR(str), RSTRING_PTR(str2), (size_t)RSTRING_LEN(str2) + termlen);
|
|
STR_SET_EMBED_LEN(str, RSTRING_LEN(str2));
|
|
rb_enc_associate(str, enc);
|
|
ENC_CODERANGE_SET(str, cr);
|
|
}
|
|
else {
|
|
STR_SET_NOEMBED(str);
|
|
FL_UNSET(str, STR_SHARED);
|
|
RSTRING(str)->as.heap.ptr = RSTRING_PTR(str2);
|
|
RSTRING(str)->as.heap.len = RSTRING_LEN(str2);
|
|
|
|
if (FL_TEST(str2, STR_SHARED)) {
|
|
VALUE shared = RSTRING(str2)->as.heap.aux.shared;
|
|
STR_SET_SHARED(str, shared);
|
|
}
|
|
else {
|
|
RSTRING(str)->as.heap.aux.capa = RSTRING(str2)->as.heap.aux.capa;
|
|
}
|
|
|
|
/* abandon str2 */
|
|
STR_SET_EMBED(str2);
|
|
RSTRING_PTR(str2)[0] = 0;
|
|
STR_SET_EMBED_LEN(str2, 0);
|
|
rb_enc_associate(str, enc);
|
|
ENC_CODERANGE_SET(str, cr);
|
|
}
|
|
}
|
|
|
|
VALUE
|
|
rb_obj_as_string(VALUE obj)
|
|
{
|
|
VALUE str;
|
|
|
|
if (RB_TYPE_P(obj, T_STRING)) {
|
|
return obj;
|
|
}
|
|
str = rb_funcall(obj, idTo_s, 0);
|
|
return rb_obj_as_string_result(str, obj);
|
|
}
|
|
|
|
MJIT_FUNC_EXPORTED VALUE
|
|
rb_obj_as_string_result(VALUE str, VALUE obj)
|
|
{
|
|
if (!RB_TYPE_P(str, T_STRING))
|
|
return rb_any_to_s(obj);
|
|
return str;
|
|
}
|
|
|
|
static VALUE
|
|
str_replace(VALUE str, VALUE str2)
|
|
{
|
|
long len;
|
|
|
|
len = RSTRING_LEN(str2);
|
|
if (STR_SHARED_P(str2)) {
|
|
VALUE shared = RSTRING(str2)->as.heap.aux.shared;
|
|
assert(OBJ_FROZEN(shared));
|
|
STR_SET_NOEMBED(str);
|
|
RSTRING(str)->as.heap.len = len;
|
|
RSTRING(str)->as.heap.ptr = RSTRING_PTR(str2);
|
|
STR_SET_SHARED(str, shared);
|
|
rb_enc_cr_str_exact_copy(str, str2);
|
|
}
|
|
else {
|
|
str_replace_shared(str, str2);
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
static inline VALUE
|
|
ec_str_alloc(struct rb_execution_context_struct *ec, VALUE klass)
|
|
{
|
|
RB_EC_NEWOBJ_OF(ec, str, struct RString, klass, T_STRING | (RGENGC_WB_PROTECTED_STRING ? FL_WB_PROTECTED : 0));
|
|
return (VALUE)str;
|
|
}
|
|
|
|
static inline VALUE
|
|
str_duplicate_setup(VALUE klass, VALUE str, VALUE dup)
|
|
{
|
|
enum {embed_size = RSTRING_EMBED_LEN_MAX + 1};
|
|
const VALUE flag_mask =
|
|
RSTRING_NOEMBED | RSTRING_EMBED_LEN_MASK |
|
|
ENC_CODERANGE_MASK | ENCODING_MASK |
|
|
FL_FREEZE
|
|
;
|
|
VALUE flags = FL_TEST_RAW(str, flag_mask);
|
|
int encidx = 0;
|
|
MEMCPY(RSTRING(dup)->as.ary, RSTRING(str)->as.ary,
|
|
char, embed_size);
|
|
if (flags & STR_NOEMBED) {
|
|
if (FL_TEST_RAW(str, STR_SHARED)) {
|
|
str = RSTRING(str)->as.heap.aux.shared;
|
|
}
|
|
else if (UNLIKELY(!(flags & FL_FREEZE))) {
|
|
str = str_new_frozen(klass, str);
|
|
flags = FL_TEST_RAW(str, flag_mask);
|
|
}
|
|
if (flags & STR_NOEMBED) {
|
|
RB_OBJ_WRITE(dup, &RSTRING(dup)->as.heap.aux.shared, str);
|
|
flags |= STR_SHARED;
|
|
}
|
|
else {
|
|
MEMCPY(RSTRING(dup)->as.ary, RSTRING(str)->as.ary,
|
|
char, embed_size);
|
|
}
|
|
}
|
|
if ((flags & ENCODING_MASK) == (ENCODING_INLINE_MAX<<ENCODING_SHIFT)) {
|
|
encidx = rb_enc_get_index(str);
|
|
flags &= ~ENCODING_MASK;
|
|
}
|
|
FL_SET_RAW(dup, flags & ~FL_FREEZE);
|
|
if (encidx) rb_enc_associate_index(dup, encidx);
|
|
return dup;
|
|
}
|
|
|
|
static inline VALUE
|
|
ec_str_duplicate(struct rb_execution_context_struct *ec, VALUE klass, VALUE str)
|
|
{
|
|
VALUE dup = ec_str_alloc(ec, klass);
|
|
return str_duplicate_setup(klass, str, dup);
|
|
}
|
|
|
|
static inline VALUE
|
|
str_duplicate(VALUE klass, VALUE str)
|
|
{
|
|
VALUE dup = str_alloc(klass);
|
|
return str_duplicate_setup(klass, str, dup);
|
|
}
|
|
|
|
VALUE
|
|
rb_str_dup(VALUE str)
|
|
{
|
|
return str_duplicate(rb_obj_class(str), str);
|
|
}
|
|
|
|
VALUE
|
|
rb_str_resurrect(VALUE str)
|
|
{
|
|
RUBY_DTRACE_CREATE_HOOK(STRING, RSTRING_LEN(str));
|
|
return str_duplicate(rb_cString, str);
|
|
}
|
|
|
|
VALUE
|
|
rb_ec_str_resurrect(struct rb_execution_context_struct *ec, VALUE str)
|
|
{
|
|
RUBY_DTRACE_CREATE_HOOK(STRING, RSTRING_LEN(str));
|
|
return ec_str_duplicate(ec, rb_cString, str);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* String.new(string = '') -> new_string
|
|
* String.new(string = '', encoding: encoding) -> new_string
|
|
* String.new(string = '', capacity: size) -> new_string
|
|
*
|
|
* Returns a new \String that is a copy of +string+.
|
|
*
|
|
* With no arguments, returns the empty string with the Encoding <tt>ASCII-8BIT</tt>:
|
|
* s = String.new
|
|
* s # => ""
|
|
* s.encoding # => #<Encoding:ASCII-8BIT>
|
|
*
|
|
* With the single \String argument +string+, returns a copy of +string+
|
|
* with the same encoding as +string+:
|
|
* s = String.new("Que veut dire \u{e7}a?")
|
|
* s # => "Que veut dire \u{e7}a?"
|
|
* s.encoding # => #<Encoding:UTF-8>
|
|
*
|
|
* Literal strings like <tt>""</tt> or here-documents always use
|
|
* {script encoding}[Encoding.html#class-Encoding-label-Script+encoding], unlike String.new.
|
|
*
|
|
* With keyword +encoding+, returns a copy of +str+
|
|
* with the specified encoding:
|
|
* s = String.new(encoding: 'ASCII')
|
|
* s.encoding # => #<Encoding:US-ASCII>
|
|
* s = String.new('foo', encoding: 'ASCII')
|
|
* s.encoding # => #<Encoding:US-ASCII>
|
|
*
|
|
* Note that these are equivalent:
|
|
* s0 = String.new('foo', encoding: 'ASCII')
|
|
* s1 = 'foo'.force_encoding('ASCII')
|
|
* s0.encoding == s1.encoding # => true
|
|
*
|
|
* With keyword +capacity+, returns a copy of +str+;
|
|
* the given +capacity+ may set the size of the internal buffer,
|
|
* which may affect performance:
|
|
* String.new(capacity: 1) # => ""
|
|
* String.new(capacity: 4096) # => ""
|
|
*
|
|
* The +string+, +encoding+, and +capacity+ arguments may all be used together:
|
|
* String.new('hello', encoding: 'UTF-8', capacity: 25)
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_init(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
static ID keyword_ids[2];
|
|
VALUE orig, opt, venc, vcapa;
|
|
VALUE kwargs[2];
|
|
rb_encoding *enc = 0;
|
|
int n;
|
|
|
|
if (!keyword_ids[0]) {
|
|
keyword_ids[0] = rb_id_encoding();
|
|
CONST_ID(keyword_ids[1], "capacity");
|
|
}
|
|
|
|
n = rb_scan_args(argc, argv, "01:", &orig, &opt);
|
|
if (!NIL_P(opt)) {
|
|
rb_get_kwargs(opt, keyword_ids, 0, 2, kwargs);
|
|
venc = kwargs[0];
|
|
vcapa = kwargs[1];
|
|
if (venc != Qundef && !NIL_P(venc)) {
|
|
enc = rb_to_encoding(venc);
|
|
}
|
|
if (vcapa != Qundef && !NIL_P(vcapa)) {
|
|
long capa = NUM2LONG(vcapa);
|
|
long len = 0;
|
|
int termlen = enc ? rb_enc_mbminlen(enc) : 1;
|
|
|
|
if (capa < STR_BUF_MIN_SIZE) {
|
|
capa = STR_BUF_MIN_SIZE;
|
|
}
|
|
if (n == 1) {
|
|
StringValue(orig);
|
|
len = RSTRING_LEN(orig);
|
|
if (capa < len) {
|
|
capa = len;
|
|
}
|
|
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);
|
|
memcpy(new_ptr, RSTRING(str)->as.ary, RSTRING_EMBED_LEN_MAX + 1);
|
|
RSTRING(str)->as.heap.ptr = new_ptr;
|
|
}
|
|
else if (FL_TEST(str, STR_SHARED|STR_NOFREE)) {
|
|
const size_t size = (size_t)capa + termlen;
|
|
const char *const old_ptr = RSTRING_PTR(str);
|
|
const size_t osize = RSTRING(str)->as.heap.len + TERM_LEN(str);
|
|
char *new_ptr = ALLOC_N(char, (size_t)capa + termlen);
|
|
memcpy(new_ptr, old_ptr, osize < size ? osize : size);
|
|
FL_UNSET_RAW(str, STR_SHARED);
|
|
RSTRING(str)->as.heap.ptr = new_ptr;
|
|
}
|
|
else if (STR_HEAP_SIZE(str) != (size_t)capa + termlen) {
|
|
SIZED_REALLOC_N(RSTRING(str)->as.heap.ptr, char,
|
|
(size_t)capa + termlen, STR_HEAP_SIZE(str));
|
|
}
|
|
RSTRING(str)->as.heap.len = len;
|
|
TERM_FILL(&RSTRING(str)->as.heap.ptr[len], termlen);
|
|
if (n == 1) {
|
|
memcpy(RSTRING(str)->as.heap.ptr, RSTRING_PTR(orig), len);
|
|
rb_enc_cr_str_exact_copy(str, orig);
|
|
}
|
|
FL_SET(str, STR_NOEMBED);
|
|
RSTRING(str)->as.heap.aux.capa = capa;
|
|
}
|
|
else if (n == 1) {
|
|
rb_str_replace(str, orig);
|
|
}
|
|
if (enc) {
|
|
rb_enc_associate(str, enc);
|
|
ENC_CODERANGE_CLEAR(str);
|
|
}
|
|
}
|
|
else if (n == 1) {
|
|
rb_str_replace(str, orig);
|
|
}
|
|
return str;
|
|
}
|
|
|
|
#ifdef NONASCII_MASK
|
|
#define is_utf8_lead_byte(c) (((c)&0xC0) != 0x80)
|
|
|
|
/*
|
|
* UTF-8 leading bytes have either 0xxxxxxx or 11xxxxxx
|
|
* bit representation. (see https://en.wikipedia.org/wiki/UTF-8)
|
|
* Therefore, the following pseudocode can detect UTF-8 leading bytes.
|
|
*
|
|
* if (!(byte & 0x80))
|
|
* byte |= 0x40; // turn on bit6
|
|
* return ((byte>>6) & 1); // bit6 represent whether this byte is leading or not.
|
|
*
|
|
* This function calculates whether a byte is leading or not for all bytes
|
|
* in the argument word by concurrently using the above logic, and then
|
|
* adds up the number of leading bytes in the word.
|
|
*/
|
|
static inline uintptr_t
|
|
count_utf8_lead_bytes_with_word(const uintptr_t *s)
|
|
{
|
|
uintptr_t d = *s;
|
|
|
|
/* Transform so that bit0 indicates whether we have a UTF-8 leading byte or not. */
|
|
d = (d>>6) | (~d>>7);
|
|
d &= NONASCII_MASK >> 7;
|
|
|
|
/* Gather all bytes. */
|
|
#if defined(HAVE_BUILTIN___BUILTIN_POPCOUNT) && defined(__POPCNT__)
|
|
/* use only if it can use POPCNT */
|
|
return rb_popcount_intptr(d);
|
|
#else
|
|
d += (d>>8);
|
|
d += (d>>16);
|
|
# if SIZEOF_VOIDP == 8
|
|
d += (d>>32);
|
|
# endif
|
|
return (d&0xF);
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
static inline long
|
|
enc_strlen(const char *p, const char *e, rb_encoding *enc, int cr)
|
|
{
|
|
long c;
|
|
const char *q;
|
|
|
|
if (rb_enc_mbmaxlen(enc) == rb_enc_mbminlen(enc)) {
|
|
long diff = (long)(e - p);
|
|
return diff / rb_enc_mbminlen(enc) + !!(diff % rb_enc_mbminlen(enc));
|
|
}
|
|
#ifdef NONASCII_MASK
|
|
else if (cr == ENC_CODERANGE_VALID && enc == rb_utf8_encoding()) {
|
|
uintptr_t len = 0;
|
|
if ((int)sizeof(uintptr_t) * 2 < e - p) {
|
|
const uintptr_t *s, *t;
|
|
const uintptr_t lowbits = sizeof(uintptr_t) - 1;
|
|
s = (const uintptr_t*)(~lowbits & ((uintptr_t)p + lowbits));
|
|
t = (const uintptr_t*)(~lowbits & (uintptr_t)e);
|
|
while (p < (const char *)s) {
|
|
if (is_utf8_lead_byte(*p)) len++;
|
|
p++;
|
|
}
|
|
while (s < t) {
|
|
len += count_utf8_lead_bytes_with_word(s);
|
|
s++;
|
|
}
|
|
p = (const char *)s;
|
|
}
|
|
while (p < e) {
|
|
if (is_utf8_lead_byte(*p)) len++;
|
|
p++;
|
|
}
|
|
return (long)len;
|
|
}
|
|
#endif
|
|
else if (rb_enc_asciicompat(enc)) {
|
|
c = 0;
|
|
if (ENC_CODERANGE_CLEAN_P(cr)) {
|
|
while (p < e) {
|
|
if (ISASCII(*p)) {
|
|
q = search_nonascii(p, e);
|
|
if (!q)
|
|
return c + (e - p);
|
|
c += q - p;
|
|
p = q;
|
|
}
|
|
p += rb_enc_fast_mbclen(p, e, enc);
|
|
c++;
|
|
}
|
|
}
|
|
else {
|
|
while (p < e) {
|
|
if (ISASCII(*p)) {
|
|
q = search_nonascii(p, e);
|
|
if (!q)
|
|
return c + (e - p);
|
|
c += q - p;
|
|
p = q;
|
|
}
|
|
p += rb_enc_mbclen(p, e, enc);
|
|
c++;
|
|
}
|
|
}
|
|
return c;
|
|
}
|
|
|
|
for (c=0; p<e; c++) {
|
|
p += rb_enc_mbclen(p, e, enc);
|
|
}
|
|
return c;
|
|
}
|
|
|
|
long
|
|
rb_enc_strlen(const char *p, const char *e, rb_encoding *enc)
|
|
{
|
|
return enc_strlen(p, e, enc, ENC_CODERANGE_UNKNOWN);
|
|
}
|
|
|
|
/* To get strlen with cr
|
|
* Note that given cr is not used.
|
|
*/
|
|
long
|
|
rb_enc_strlen_cr(const char *p, const char *e, rb_encoding *enc, int *cr)
|
|
{
|
|
long c;
|
|
const char *q;
|
|
int ret;
|
|
|
|
*cr = 0;
|
|
if (rb_enc_mbmaxlen(enc) == rb_enc_mbminlen(enc)) {
|
|
long diff = (long)(e - p);
|
|
return diff / rb_enc_mbminlen(enc) + !!(diff % rb_enc_mbminlen(enc));
|
|
}
|
|
else if (rb_enc_asciicompat(enc)) {
|
|
c = 0;
|
|
while (p < e) {
|
|
if (ISASCII(*p)) {
|
|
q = search_nonascii(p, e);
|
|
if (!q) {
|
|
if (!*cr) *cr = ENC_CODERANGE_7BIT;
|
|
return c + (e - p);
|
|
}
|
|
c += q - p;
|
|
p = q;
|
|
}
|
|
ret = rb_enc_precise_mbclen(p, e, enc);
|
|
if (MBCLEN_CHARFOUND_P(ret)) {
|
|
*cr |= ENC_CODERANGE_VALID;
|
|
p += MBCLEN_CHARFOUND_LEN(ret);
|
|
}
|
|
else {
|
|
*cr = ENC_CODERANGE_BROKEN;
|
|
p++;
|
|
}
|
|
c++;
|
|
}
|
|
if (!*cr) *cr = ENC_CODERANGE_7BIT;
|
|
return c;
|
|
}
|
|
|
|
for (c=0; p<e; c++) {
|
|
ret = rb_enc_precise_mbclen(p, e, enc);
|
|
if (MBCLEN_CHARFOUND_P(ret)) {
|
|
*cr |= ENC_CODERANGE_VALID;
|
|
p += MBCLEN_CHARFOUND_LEN(ret);
|
|
}
|
|
else {
|
|
*cr = ENC_CODERANGE_BROKEN;
|
|
if (p + rb_enc_mbminlen(enc) <= e)
|
|
p += rb_enc_mbminlen(enc);
|
|
else
|
|
p = e;
|
|
}
|
|
}
|
|
if (!*cr) *cr = ENC_CODERANGE_7BIT;
|
|
return c;
|
|
}
|
|
|
|
/* enc must be str's enc or rb_enc_check(str, str2) */
|
|
static long
|
|
str_strlen(VALUE str, rb_encoding *enc)
|
|
{
|
|
const char *p, *e;
|
|
int cr;
|
|
|
|
if (single_byte_optimizable(str)) return RSTRING_LEN(str);
|
|
if (!enc) enc = STR_ENC_GET(str);
|
|
p = RSTRING_PTR(str);
|
|
e = RSTRING_END(str);
|
|
cr = ENC_CODERANGE(str);
|
|
|
|
if (cr == ENC_CODERANGE_UNKNOWN) {
|
|
long n = rb_enc_strlen_cr(p, e, enc, &cr);
|
|
if (cr) ENC_CODERANGE_SET(str, cr);
|
|
return n;
|
|
}
|
|
else {
|
|
return enc_strlen(p, e, enc, cr);
|
|
}
|
|
}
|
|
|
|
long
|
|
rb_str_strlen(VALUE str)
|
|
{
|
|
return str_strlen(str, NULL);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* string.length -> integer
|
|
*
|
|
* Returns the count of characters (not bytes) in +self+:
|
|
* "\x80\u3042".length # => 2
|
|
* "hello".length # => 5
|
|
*
|
|
* String#size is an alias for String#length.
|
|
*
|
|
* Related: String#bytesize.
|
|
*/
|
|
|
|
VALUE
|
|
rb_str_length(VALUE str)
|
|
{
|
|
return LONG2NUM(str_strlen(str, NULL));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* string.bytesize -> integer
|
|
*
|
|
* Returns the count of bytes in +self+:
|
|
* "\x80\u3042".bytesize # => 4
|
|
* "hello".bytesize # => 5
|
|
*
|
|
* Related: String#length.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_bytesize(VALUE str)
|
|
{
|
|
return LONG2NUM(RSTRING_LEN(str));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* string.empty? -> true or false
|
|
*
|
|
* Returns +true+ if the length of +self+ is zero, +false+ otherwise:
|
|
* "hello".empty? # => false
|
|
* " ".empty? # => false
|
|
* "".empty? # => true
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_empty(VALUE str)
|
|
{
|
|
if (RSTRING_LEN(str) == 0)
|
|
return Qtrue;
|
|
return Qfalse;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* string + other_string -> new_string
|
|
*
|
|
* Returns a new \String containing +other_string+ concatenated to +self+:
|
|
* "Hello from " + self.to_s # => "Hello from main"
|
|
*/
|
|
|
|
VALUE
|
|
rb_str_plus(VALUE str1, VALUE str2)
|
|
{
|
|
VALUE str3;
|
|
rb_encoding *enc;
|
|
char *ptr1, *ptr2, *ptr3;
|
|
long len1, len2;
|
|
int termlen;
|
|
|
|
StringValue(str2);
|
|
enc = rb_enc_check_str(str1, str2);
|
|
RSTRING_GETMEM(str1, ptr1, len1);
|
|
RSTRING_GETMEM(str2, ptr2, len2);
|
|
termlen = rb_enc_mbminlen(enc);
|
|
if (len1 > LONG_MAX - len2) {
|
|
rb_raise(rb_eArgError, "string size too big");
|
|
}
|
|
str3 = str_new0(rb_cString, 0, len1+len2, termlen);
|
|
ptr3 = RSTRING_PTR(str3);
|
|
memcpy(ptr3, ptr1, len1);
|
|
memcpy(ptr3+len1, ptr2, len2);
|
|
TERM_FILL(&ptr3[len1+len2], termlen);
|
|
|
|
ENCODING_CODERANGE_SET(str3, rb_enc_to_index(enc),
|
|
ENC_CODERANGE_AND(ENC_CODERANGE(str1), ENC_CODERANGE(str2)));
|
|
RB_GC_GUARD(str1);
|
|
RB_GC_GUARD(str2);
|
|
return str3;
|
|
}
|
|
|
|
/* A variant of rb_str_plus that does not raise but return Qundef instead. */
|
|
MJIT_FUNC_EXPORTED VALUE
|
|
rb_str_opt_plus(VALUE str1, VALUE str2)
|
|
{
|
|
assert(RBASIC_CLASS(str1) == rb_cString);
|
|
assert(RBASIC_CLASS(str2) == rb_cString);
|
|
long len1, len2;
|
|
MAYBE_UNUSED(char) *ptr1, *ptr2;
|
|
RSTRING_GETMEM(str1, ptr1, len1);
|
|
RSTRING_GETMEM(str2, ptr2, len2);
|
|
int enc1 = rb_enc_get_index(str1);
|
|
int enc2 = rb_enc_get_index(str2);
|
|
|
|
if (enc1 < 0) {
|
|
return Qundef;
|
|
}
|
|
else if (enc2 < 0) {
|
|
return Qundef;
|
|
}
|
|
else if (enc1 != enc2) {
|
|
return Qundef;
|
|
}
|
|
else if (len1 > LONG_MAX - len2) {
|
|
return Qundef;
|
|
}
|
|
else {
|
|
return rb_str_plus(str1, str2);
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* string * integer -> new_string
|
|
*
|
|
* Returns a new \String containing +integer+ copies of +self+:
|
|
* "Ho! " * 3 # => "Ho! Ho! Ho! "
|
|
* "Ho! " * 0 # => ""
|
|
*/
|
|
|
|
VALUE
|
|
rb_str_times(VALUE str, VALUE times)
|
|
{
|
|
VALUE str2;
|
|
long n, len;
|
|
char *ptr2;
|
|
int termlen;
|
|
|
|
if (times == INT2FIX(1)) {
|
|
return str_duplicate(rb_cString, str);
|
|
}
|
|
if (times == INT2FIX(0)) {
|
|
str2 = str_alloc(rb_cString);
|
|
rb_enc_copy(str2, str);
|
|
return str2;
|
|
}
|
|
len = NUM2LONG(times);
|
|
if (len < 0) {
|
|
rb_raise(rb_eArgError, "negative argument");
|
|
}
|
|
if (RSTRING_LEN(str) == 1 && RSTRING_PTR(str)[0] == 0) {
|
|
str2 = str_alloc(rb_cString);
|
|
if (!STR_EMBEDDABLE_P(len, 1)) {
|
|
RSTRING(str2)->as.heap.aux.capa = len;
|
|
RSTRING(str2)->as.heap.ptr = ZALLOC_N(char, (size_t)len + 1);
|
|
STR_SET_NOEMBED(str2);
|
|
}
|
|
STR_SET_LEN(str2, len);
|
|
rb_enc_copy(str2, str);
|
|
return str2;
|
|
}
|
|
if (len && LONG_MAX/len < RSTRING_LEN(str)) {
|
|
rb_raise(rb_eArgError, "argument too big");
|
|
}
|
|
|
|
len *= RSTRING_LEN(str);
|
|
termlen = TERM_LEN(str);
|
|
str2 = str_new0(rb_cString, 0, len, termlen);
|
|
ptr2 = RSTRING_PTR(str2);
|
|
if (len) {
|
|
n = RSTRING_LEN(str);
|
|
memcpy(ptr2, RSTRING_PTR(str), n);
|
|
while (n <= len/2) {
|
|
memcpy(ptr2 + n, ptr2, n);
|
|
n *= 2;
|
|
}
|
|
memcpy(ptr2 + n, ptr2, len-n);
|
|
}
|
|
STR_SET_LEN(str2, len);
|
|
TERM_FILL(&ptr2[len], termlen);
|
|
rb_enc_cr_str_copy_for_substr(str2, str);
|
|
|
|
return str2;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* string % object -> new_string
|
|
*
|
|
* Returns the result of formatting +object+ into the format specification +self+
|
|
* (see Kernel#sprintf for formatting details):
|
|
* "%05d" % 123 # => "00123"
|
|
*
|
|
* If +self+ contains multiple substitutions, +object+ must be
|
|
* an \Array or \Hash containing the values to be substituted:
|
|
* "%-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"
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_format_m(VALUE str, VALUE arg)
|
|
{
|
|
VALUE tmp = rb_check_array_type(arg);
|
|
|
|
if (!NIL_P(tmp)) {
|
|
return rb_str_format(RARRAY_LENINT(tmp), RARRAY_CONST_PTR(tmp), str);
|
|
}
|
|
return rb_str_format(1, &arg, str);
|
|
}
|
|
|
|
static inline void
|
|
rb_check_lockedtmp(VALUE str)
|
|
{
|
|
if (FL_TEST(str, STR_TMPLOCK)) {
|
|
rb_raise(rb_eRuntimeError, "can't modify string; temporarily locked");
|
|
}
|
|
}
|
|
|
|
static inline void
|
|
str_modifiable(VALUE 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;
|
|
}
|
|
else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static inline int
|
|
str_independent(VALUE str)
|
|
{
|
|
str_modifiable(str);
|
|
return !str_dependent_p(str);
|
|
}
|
|
|
|
static void
|
|
str_make_independent_expand(VALUE str, long len, long expand, const int termlen)
|
|
{
|
|
char *ptr;
|
|
char *oldptr;
|
|
long capa = len + expand;
|
|
|
|
if (len > capa) len = capa;
|
|
|
|
if (!STR_EMBED_P(str) && STR_EMBEDDABLE_P(capa, termlen)) {
|
|
ptr = RSTRING(str)->as.heap.ptr;
|
|
STR_SET_EMBED(str);
|
|
memcpy(RSTRING(str)->as.ary, ptr, len);
|
|
TERM_FILL(RSTRING(str)->as.ary + len, termlen);
|
|
STR_SET_EMBED_LEN(str, len);
|
|
return;
|
|
}
|
|
|
|
ptr = ALLOC_N(char, (size_t)capa + termlen);
|
|
oldptr = RSTRING_PTR(str);
|
|
if (oldptr) {
|
|
memcpy(ptr, oldptr, len);
|
|
}
|
|
if (FL_TEST_RAW(str, STR_NOEMBED|STR_NOFREE|STR_SHARED) == STR_NOEMBED) {
|
|
xfree(oldptr);
|
|
}
|
|
STR_SET_NOEMBED(str);
|
|
FL_UNSET(str, STR_SHARED|STR_NOFREE);
|
|
TERM_FILL(ptr + len, termlen);
|
|
RSTRING(str)->as.heap.ptr = ptr;
|
|
RSTRING(str)->as.heap.len = len;
|
|
RSTRING(str)->as.heap.aux.capa = capa;
|
|
}
|
|
|
|
void
|
|
rb_str_modify(VALUE str)
|
|
{
|
|
if (!str_independent(str))
|
|
str_make_independent(str);
|
|
ENC_CODERANGE_CLEAR(str);
|
|
}
|
|
|
|
void
|
|
rb_str_modify_expand(VALUE str, long expand)
|
|
{
|
|
int termlen = TERM_LEN(str);
|
|
long len = RSTRING_LEN(str);
|
|
|
|
if (expand < 0) {
|
|
rb_raise(rb_eArgError, "negative expanding string size");
|
|
}
|
|
if (expand >= LONG_MAX - len) {
|
|
rb_raise(rb_eArgError, "string size too big");
|
|
}
|
|
|
|
if (!str_independent(str)) {
|
|
str_make_independent_expand(str, len, expand, termlen);
|
|
}
|
|
else if (expand > 0) {
|
|
RESIZE_CAPA_TERM(str, len + expand, termlen);
|
|
}
|
|
ENC_CODERANGE_CLEAR(str);
|
|
}
|
|
|
|
/* As rb_str_modify(), but don't clear coderange */
|
|
static void
|
|
str_modify_keep_cr(VALUE str)
|
|
{
|
|
if (!str_independent(str))
|
|
str_make_independent(str);
|
|
if (ENC_CODERANGE(str) == ENC_CODERANGE_BROKEN)
|
|
/* Force re-scan later */
|
|
ENC_CODERANGE_CLEAR(str);
|
|
}
|
|
|
|
static inline void
|
|
str_discard(VALUE str)
|
|
{
|
|
str_modifiable(str);
|
|
if (!STR_EMBED_P(str) && !FL_TEST(str, STR_SHARED|STR_NOFREE)) {
|
|
ruby_sized_xfree(STR_HEAP_PTR(str), STR_HEAP_SIZE(str));
|
|
RSTRING(str)->as.heap.ptr = 0;
|
|
RSTRING(str)->as.heap.len = 0;
|
|
}
|
|
}
|
|
|
|
void
|
|
rb_must_asciicompat(VALUE str)
|
|
{
|
|
rb_encoding *enc = rb_enc_get(str);
|
|
if (!rb_enc_asciicompat(enc)) {
|
|
rb_raise(rb_eEncCompatError, "ASCII incompatible encoding: %s", rb_enc_name(enc));
|
|
}
|
|
}
|
|
|
|
VALUE
|
|
rb_string_value(volatile VALUE *ptr)
|
|
{
|
|
VALUE s = *ptr;
|
|
if (!RB_TYPE_P(s, T_STRING)) {
|
|
s = rb_str_to_str(s);
|
|
*ptr = s;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
char *
|
|
rb_string_value_ptr(volatile VALUE *ptr)
|
|
{
|
|
VALUE str = rb_string_value(ptr);
|
|
return RSTRING_PTR(str);
|
|
}
|
|
|
|
static int
|
|
zero_filled(const char *s, int n)
|
|
{
|
|
for (; n > 0; --n) {
|
|
if (*s++) return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static const char *
|
|
str_null_char(const char *s, long len, const int minlen, rb_encoding *enc)
|
|
{
|
|
const char *e = s + len;
|
|
|
|
for (; s + minlen <= e; s += rb_enc_mbclen(s, e, enc)) {
|
|
if (zero_filled(s, minlen)) return s;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static char *
|
|
str_fill_term(VALUE str, char *s, long len, int termlen)
|
|
{
|
|
/* This function assumes that (capa + termlen) bytes of memory
|
|
* is allocated, like many other functions in this file.
|
|
*/
|
|
if (str_dependent_p(str)) {
|
|
if (!zero_filled(s + len, termlen))
|
|
str_make_independent_expand(str, len, 0L, termlen);
|
|
}
|
|
else {
|
|
TERM_FILL(s + len, termlen);
|
|
return s;
|
|
}
|
|
return RSTRING_PTR(str);
|
|
}
|
|
|
|
void
|
|
rb_str_change_terminator_length(VALUE str, const int oldtermlen, const int termlen)
|
|
{
|
|
long capa = str_capacity(str, oldtermlen) + oldtermlen;
|
|
long len = RSTRING_LEN(str);
|
|
|
|
assert(capa >= len);
|
|
if (capa - len < termlen) {
|
|
rb_check_lockedtmp(str);
|
|
str_make_independent_expand(str, len, 0L, termlen);
|
|
}
|
|
else if (str_dependent_p(str)) {
|
|
if (termlen > oldtermlen)
|
|
str_make_independent_expand(str, len, 0L, termlen);
|
|
}
|
|
else {
|
|
if (!STR_EMBED_P(str)) {
|
|
/* modify capa instead of realloc */
|
|
assert(!FL_TEST((str), STR_SHARED));
|
|
RSTRING(str)->as.heap.aux.capa = capa - termlen;
|
|
}
|
|
if (termlen > oldtermlen) {
|
|
TERM_FILL(RSTRING_PTR(str) + len, termlen);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static char *
|
|
str_null_check(VALUE str, int *w)
|
|
{
|
|
char *s = RSTRING_PTR(str);
|
|
long len = RSTRING_LEN(str);
|
|
rb_encoding *enc = rb_enc_get(str);
|
|
const int minlen = rb_enc_mbminlen(enc);
|
|
|
|
if (minlen > 1) {
|
|
*w = 1;
|
|
if (str_null_char(s, len, minlen, enc)) {
|
|
return NULL;
|
|
}
|
|
return str_fill_term(str, s, len, minlen);
|
|
}
|
|
*w = 0;
|
|
if (!s || memchr(s, 0, len)) {
|
|
return NULL;
|
|
}
|
|
if (s[len]) {
|
|
s = str_fill_term(str, s, len, minlen);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
char *
|
|
rb_str_to_cstr(VALUE str)
|
|
{
|
|
int w;
|
|
return str_null_check(str, &w);
|
|
}
|
|
|
|
char *
|
|
rb_string_value_cstr(volatile VALUE *ptr)
|
|
{
|
|
VALUE str = rb_string_value(ptr);
|
|
int w;
|
|
char *s = str_null_check(str, &w);
|
|
if (!s) {
|
|
if (w) {
|
|
rb_raise(rb_eArgError, "string contains null char");
|
|
}
|
|
rb_raise(rb_eArgError, "string contains null byte");
|
|
}
|
|
return s;
|
|
}
|
|
|
|
char *
|
|
rb_str_fill_terminator(VALUE str, const int newminlen)
|
|
{
|
|
char *s = RSTRING_PTR(str);
|
|
long len = RSTRING_LEN(str);
|
|
return str_fill_term(str, s, len, newminlen);
|
|
}
|
|
|
|
VALUE
|
|
rb_check_string_type(VALUE str)
|
|
{
|
|
str = rb_check_convert_type_with_id(str, T_STRING, "String", idTo_str);
|
|
return str;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* String.try_convert(object) -> object, new_string, or nil
|
|
*
|
|
* If +object+ is a \String object, returns +object+.
|
|
*
|
|
* 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.
|
|
*/
|
|
static VALUE
|
|
rb_str_s_try_convert(VALUE dummy, VALUE str)
|
|
{
|
|
return rb_check_string_type(str);
|
|
}
|
|
|
|
static char*
|
|
str_nth_len(const char *p, const char *e, long *nthp, rb_encoding *enc)
|
|
{
|
|
long nth = *nthp;
|
|
if (rb_enc_mbmaxlen(enc) == 1) {
|
|
p += nth;
|
|
}
|
|
else if (rb_enc_mbmaxlen(enc) == rb_enc_mbminlen(enc)) {
|
|
p += nth * rb_enc_mbmaxlen(enc);
|
|
}
|
|
else if (rb_enc_asciicompat(enc)) {
|
|
const char *p2, *e2;
|
|
int n;
|
|
|
|
while (p < e && 0 < nth) {
|
|
e2 = p + nth;
|
|
if (e < e2) {
|
|
*nthp = nth;
|
|
return (char *)e;
|
|
}
|
|
if (ISASCII(*p)) {
|
|
p2 = search_nonascii(p, e2);
|
|
if (!p2) {
|
|
nth -= e2 - p;
|
|
*nthp = nth;
|
|
return (char *)e2;
|
|
}
|
|
nth -= p2 - p;
|
|
p = p2;
|
|
}
|
|
n = rb_enc_mbclen(p, e, enc);
|
|
p += n;
|
|
nth--;
|
|
}
|
|
*nthp = nth;
|
|
if (nth != 0) {
|
|
return (char *)e;
|
|
}
|
|
return (char *)p;
|
|
}
|
|
else {
|
|
while (p < e && nth--) {
|
|
p += rb_enc_mbclen(p, e, enc);
|
|
}
|
|
}
|
|
if (p > e) p = e;
|
|
*nthp = nth;
|
|
return (char*)p;
|
|
}
|
|
|
|
char*
|
|
rb_enc_nth(const char *p, const char *e, long nth, rb_encoding *enc)
|
|
{
|
|
return str_nth_len(p, e, &nth, enc);
|
|
}
|
|
|
|
static char*
|
|
str_nth(const char *p, const char *e, long nth, rb_encoding *enc, int singlebyte)
|
|
{
|
|
if (singlebyte)
|
|
p += nth;
|
|
else {
|
|
p = str_nth_len(p, e, &nth, enc);
|
|
}
|
|
if (!p) return 0;
|
|
if (p > e) p = e;
|
|
return (char *)p;
|
|
}
|
|
|
|
/* char offset to byte offset */
|
|
static long
|
|
str_offset(const char *p, const char *e, long nth, rb_encoding *enc, int singlebyte)
|
|
{
|
|
const char *pp = str_nth(p, e, nth, enc, singlebyte);
|
|
if (!pp) return e - p;
|
|
return pp - p;
|
|
}
|
|
|
|
long
|
|
rb_str_offset(VALUE str, long pos)
|
|
{
|
|
return str_offset(RSTRING_PTR(str), RSTRING_END(str), pos,
|
|
STR_ENC_GET(str), single_byte_optimizable(str));
|
|
}
|
|
|
|
#ifdef NONASCII_MASK
|
|
static char *
|
|
str_utf8_nth(const char *p, const char *e, long *nthp)
|
|
{
|
|
long nth = *nthp;
|
|
if ((int)SIZEOF_VOIDP * 2 < e - p && (int)SIZEOF_VOIDP * 2 < nth) {
|
|
const uintptr_t *s, *t;
|
|
const uintptr_t lowbits = SIZEOF_VOIDP - 1;
|
|
s = (const uintptr_t*)(~lowbits & ((uintptr_t)p + lowbits));
|
|
t = (const uintptr_t*)(~lowbits & (uintptr_t)e);
|
|
while (p < (const char *)s) {
|
|
if (is_utf8_lead_byte(*p)) nth--;
|
|
p++;
|
|
}
|
|
do {
|
|
nth -= count_utf8_lead_bytes_with_word(s);
|
|
s++;
|
|
} while (s < t && (int)SIZEOF_VOIDP <= nth);
|
|
p = (char *)s;
|
|
}
|
|
while (p < e) {
|
|
if (is_utf8_lead_byte(*p)) {
|
|
if (nth == 0) break;
|
|
nth--;
|
|
}
|
|
p++;
|
|
}
|
|
*nthp = nth;
|
|
return (char *)p;
|
|
}
|
|
|
|
static long
|
|
str_utf8_offset(const char *p, const char *e, long nth)
|
|
{
|
|
const char *pp = str_utf8_nth(p, e, &nth);
|
|
return pp - p;
|
|
}
|
|
#endif
|
|
|
|
/* byte offset to char offset */
|
|
long
|
|
rb_str_sublen(VALUE str, long pos)
|
|
{
|
|
if (single_byte_optimizable(str) || pos < 0)
|
|
return pos;
|
|
else {
|
|
char *p = RSTRING_PTR(str);
|
|
return enc_strlen(p, p + pos, STR_ENC_GET(str), ENC_CODERANGE(str));
|
|
}
|
|
}
|
|
|
|
VALUE
|
|
rb_str_subseq(VALUE str, long beg, long len)
|
|
{
|
|
VALUE str2;
|
|
|
|
if (!STR_EMBEDDABLE_P(len, TERM_LEN(str)) &&
|
|
SHARABLE_SUBSTRING_P(beg, len, RSTRING_LEN(str))) {
|
|
long olen;
|
|
str2 = rb_str_new_shared(rb_str_new_frozen_String(str));
|
|
RSTRING(str2)->as.heap.ptr += beg;
|
|
olen = RSTRING(str2)->as.heap.len;
|
|
if (olen > len) RSTRING(str2)->as.heap.len = len;
|
|
}
|
|
else {
|
|
str2 = rb_str_new(RSTRING_PTR(str)+beg, len);
|
|
RB_GC_GUARD(str);
|
|
}
|
|
|
|
rb_enc_cr_str_copy_for_substr(str2, str);
|
|
|
|
return str2;
|
|
}
|
|
|
|
char *
|
|
rb_str_subpos(VALUE str, long beg, long *lenp)
|
|
{
|
|
long len = *lenp;
|
|
long slen = -1L;
|
|
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 (!blen) {
|
|
len = 0;
|
|
}
|
|
if (single_byte_optimizable(str)) {
|
|
if (beg > blen) return 0;
|
|
if (beg < 0) {
|
|
beg += blen;
|
|
if (beg < 0) return 0;
|
|
}
|
|
if (len > blen - beg)
|
|
len = blen - beg;
|
|
if (len < 0) return 0;
|
|
p = s + beg;
|
|
goto end;
|
|
}
|
|
if (beg < 0) {
|
|
if (len > -beg) len = -beg;
|
|
if (-beg * rb_enc_mbmaxlen(enc) < RSTRING_LEN(str) / 8) {
|
|
beg = -beg;
|
|
while (beg-- > len && (e = rb_enc_prev_char(s, e, e, enc)) != 0);
|
|
p = e;
|
|
if (!p) return 0;
|
|
while (len-- > 0 && (p = rb_enc_prev_char(s, p, e, enc)) != 0);
|
|
if (!p) return 0;
|
|
len = e - p;
|
|
goto end;
|
|
}
|
|
else {
|
|
slen = str_strlen(str, enc);
|
|
beg += slen;
|
|
if (beg < 0) return 0;
|
|
p = s + beg;
|
|
if (len == 0) goto end;
|
|
}
|
|
}
|
|
else if (beg > 0 && beg > RSTRING_LEN(str)) {
|
|
return 0;
|
|
}
|
|
if (len == 0) {
|
|
if (beg > str_strlen(str, enc)) return 0; /* str's enc */
|
|
p = s + beg;
|
|
}
|
|
#ifdef NONASCII_MASK
|
|
else if (ENC_CODERANGE(str) == ENC_CODERANGE_VALID &&
|
|
enc == rb_utf8_encoding()) {
|
|
p = str_utf8_nth(s, e, &beg);
|
|
if (beg > 0) return 0;
|
|
len = str_utf8_offset(p, e, len);
|
|
}
|
|
#endif
|
|
else if (rb_enc_mbmaxlen(enc) == rb_enc_mbminlen(enc)) {
|
|
int char_sz = rb_enc_mbmaxlen(enc);
|
|
|
|
p = s + beg * char_sz;
|
|
if (p > e) {
|
|
return 0;
|
|
}
|
|
else if (len * char_sz > e - p)
|
|
len = e - p;
|
|
else
|
|
len *= char_sz;
|
|
}
|
|
else if ((p = str_nth_len(s, e, &beg, enc)) == e) {
|
|
if (beg > 0) return 0;
|
|
len = 0;
|
|
}
|
|
else {
|
|
len = str_offset(p, e, len, enc, 0);
|
|
}
|
|
end:
|
|
*lenp = len;
|
|
RB_GC_GUARD(str);
|
|
return p;
|
|
}
|
|
|
|
static VALUE str_substr(VALUE str, long beg, long len, int empty);
|
|
|
|
VALUE
|
|
rb_str_substr(VALUE str, long beg, long len)
|
|
{
|
|
return str_substr(str, beg, len, TRUE);
|
|
}
|
|
|
|
static VALUE
|
|
str_substr(VALUE str, long beg, long len, int empty)
|
|
{
|
|
VALUE str2;
|
|
char *p = rb_str_subpos(str, beg, &len);
|
|
|
|
if (!p) return Qnil;
|
|
if (!STR_EMBEDDABLE_P(len, TERM_LEN(str)) &&
|
|
SHARABLE_SUBSTRING_P(p, len, RSTRING_END(str))) {
|
|
long ofs = p - RSTRING_PTR(str);
|
|
str2 = rb_str_new_frozen(str);
|
|
str2 = str_new_shared(rb_cString, str2);
|
|
RSTRING(str2)->as.heap.ptr += ofs;
|
|
RSTRING(str2)->as.heap.len = len;
|
|
ENC_CODERANGE_CLEAR(str2);
|
|
}
|
|
else {
|
|
if (!len && !empty) return Qnil;
|
|
str2 = rb_str_new(p, len);
|
|
RB_GC_GUARD(str);
|
|
}
|
|
rb_enc_cr_str_copy_for_substr(str2, str);
|
|
|
|
return str2;
|
|
}
|
|
|
|
VALUE
|
|
rb_str_freeze(VALUE str)
|
|
{
|
|
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.
|
|
*
|
|
* Otherwise. returns <tt>self.dup</tt>, which is not frozen.
|
|
*/
|
|
static VALUE
|
|
str_uplus(VALUE str)
|
|
{
|
|
if (OBJ_FROZEN(str)) {
|
|
return rb_str_dup(str);
|
|
}
|
|
else {
|
|
return str;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* -string -> frozen_string
|
|
*
|
|
* Returns a frozen, possibly pre-existing copy of the string.
|
|
*
|
|
* The returned \String will be deduplicated as long as it does not have
|
|
* any instance variables set on it.
|
|
*/
|
|
static VALUE
|
|
str_uminus(VALUE str)
|
|
{
|
|
if (!BARE_STRING_P(str) && !rb_obj_frozen_p(str)) {
|
|
str = rb_str_dup(str);
|
|
}
|
|
return rb_fstring(str);
|
|
}
|
|
|
|
RUBY_ALIAS_FUNCTION(rb_str_dup_frozen(VALUE str), rb_str_new_frozen, (str))
|
|
#define rb_str_dup_frozen rb_str_new_frozen
|
|
|
|
VALUE
|
|
rb_str_locktmp(VALUE str)
|
|
{
|
|
if (FL_TEST(str, STR_TMPLOCK)) {
|
|
rb_raise(rb_eRuntimeError, "temporal locking already locked string");
|
|
}
|
|
FL_SET(str, STR_TMPLOCK);
|
|
return str;
|
|
}
|
|
|
|
VALUE
|
|
rb_str_unlocktmp(VALUE str)
|
|
{
|
|
if (!FL_TEST(str, STR_TMPLOCK)) {
|
|
rb_raise(rb_eRuntimeError, "temporal unlocking already unlocked string");
|
|
}
|
|
FL_UNSET(str, STR_TMPLOCK);
|
|
return str;
|
|
}
|
|
|
|
RUBY_FUNC_EXPORTED VALUE
|
|
rb_str_locktmp_ensure(VALUE str, VALUE (*func)(VALUE), VALUE arg)
|
|
{
|
|
rb_str_locktmp(str);
|
|
return rb_ensure(func, arg, rb_str_unlocktmp, str);
|
|
}
|
|
|
|
void
|
|
rb_str_set_len(VALUE str, long len)
|
|
{
|
|
long capa;
|
|
const int termlen = TERM_LEN(str);
|
|
|
|
str_modifiable(str);
|
|
if (STR_SHARED_P(str)) {
|
|
rb_raise(rb_eRuntimeError, "can't set length of shared string");
|
|
}
|
|
if (len > (capa = (long)str_capacity(str, termlen)) || len < 0) {
|
|
rb_bug("probable buffer overflow: %ld for %ld", len, capa);
|
|
}
|
|
STR_SET_LEN(str, len);
|
|
TERM_FILL(&RSTRING_PTR(str)[len], termlen);
|
|
}
|
|
|
|
VALUE
|
|
rb_str_resize(VALUE str, long len)
|
|
{
|
|
long slen;
|
|
int independent;
|
|
|
|
if (len < 0) {
|
|
rb_raise(rb_eArgError, "negative string size (or size too big)");
|
|
}
|
|
|
|
independent = str_independent(str);
|
|
ENC_CODERANGE_CLEAR(str);
|
|
slen = RSTRING_LEN(str);
|
|
|
|
{
|
|
long capa;
|
|
const int termlen = TERM_LEN(str);
|
|
if (STR_EMBED_P(str)) {
|
|
if (len == slen) return str;
|
|
if (STR_EMBEDDABLE_P(len, termlen)) {
|
|
STR_SET_EMBED_LEN(str, len);
|
|
TERM_FILL(RSTRING(str)->as.ary + len, termlen);
|
|
return str;
|
|
}
|
|
str_make_independent_expand(str, slen, len - slen, termlen);
|
|
}
|
|
else if (STR_EMBEDDABLE_P(len, termlen)) {
|
|
char *ptr = STR_HEAP_PTR(str);
|
|
STR_SET_EMBED(str);
|
|
if (slen > len) slen = len;
|
|
if (slen > 0) MEMCPY(RSTRING(str)->as.ary, ptr, char, slen);
|
|
TERM_FILL(RSTRING(str)->as.ary + len, termlen);
|
|
STR_SET_EMBED_LEN(str, len);
|
|
if (independent) ruby_xfree(ptr);
|
|
return str;
|
|
}
|
|
else if (!independent) {
|
|
if (len == slen) return str;
|
|
str_make_independent_expand(str, slen, len - slen, termlen);
|
|
}
|
|
else if ((capa = RSTRING(str)->as.heap.aux.capa) < len ||
|
|
(capa - len) > (len < 1024 ? len : 1024)) {
|
|
SIZED_REALLOC_N(RSTRING(str)->as.heap.ptr, char,
|
|
(size_t)len + termlen, STR_HEAP_SIZE(str));
|
|
RSTRING(str)->as.heap.aux.capa = len;
|
|
}
|
|
else if (len == slen) return str;
|
|
RSTRING(str)->as.heap.len = len;
|
|
TERM_FILL(RSTRING(str)->as.heap.ptr + len, termlen); /* sentinel */
|
|
}
|
|
return str;
|
|
}
|
|
|
|
static VALUE
|
|
str_buf_cat(VALUE str, const char *ptr, long len)
|
|
{
|
|
long capa, total, olen, off = -1;
|
|
char *sptr;
|
|
const int termlen = TERM_LEN(str);
|
|
assert(termlen < RSTRING_EMBED_LEN_MAX + 1); /* < (LONG_MAX/2) */
|
|
|
|
RSTRING_GETMEM(str, sptr, olen);
|
|
if (ptr >= sptr && ptr <= sptr + olen) {
|
|
off = ptr - sptr;
|
|
}
|
|
rb_str_modify(str);
|
|
if (len == 0) return 0;
|
|
if (STR_EMBED_P(str)) {
|
|
capa = RSTRING_EMBED_LEN_MAX + 1 - termlen;
|
|
sptr = RSTRING(str)->as.ary;
|
|
olen = RSTRING_EMBED_LEN(str);
|
|
}
|
|
else {
|
|
capa = RSTRING(str)->as.heap.aux.capa;
|
|
sptr = RSTRING(str)->as.heap.ptr;
|
|
olen = RSTRING(str)->as.heap.len;
|
|
}
|
|
if (olen > LONG_MAX - len) {
|
|
rb_raise(rb_eArgError, "string sizes too big");
|
|
}
|
|
total = olen + len;
|
|
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);
|
|
sptr = RSTRING_PTR(str);
|
|
}
|
|
if (off != -1) {
|
|
ptr = sptr + off;
|
|
}
|
|
memcpy(sptr + olen, ptr, len);
|
|
STR_SET_LEN(str, total);
|
|
TERM_FILL(sptr + total, termlen); /* sentinel */
|
|
|
|
return str;
|
|
}
|
|
|
|
#define str_buf_cat2(str, ptr) str_buf_cat((str), (ptr), strlen(ptr))
|
|
|
|
VALUE
|
|
rb_str_cat(VALUE str, const char *ptr, long len)
|
|
{
|
|
if (len == 0) return str;
|
|
if (len < 0) {
|
|
rb_raise(rb_eArgError, "negative string size (or size too big)");
|
|
}
|
|
return str_buf_cat(str, ptr, len);
|
|
}
|
|
|
|
VALUE
|
|
rb_str_cat_cstr(VALUE str, const char *ptr)
|
|
{
|
|
must_not_null(ptr);
|
|
return rb_str_buf_cat(str, ptr, strlen(ptr));
|
|
}
|
|
|
|
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))
|
|
|
|
static VALUE
|
|
rb_enc_cr_str_buf_cat(VALUE str, const char *ptr, long len,
|
|
int ptr_encindex, int ptr_cr, int *ptr_cr_ret)
|
|
{
|
|
int str_encindex = ENCODING_GET(str);
|
|
int res_encindex;
|
|
int str_cr, res_cr;
|
|
rb_encoding *str_enc, *ptr_enc;
|
|
|
|
str_cr = RSTRING_LEN(str) ? ENC_CODERANGE(str) : ENC_CODERANGE_7BIT;
|
|
|
|
if (str_encindex == ptr_encindex) {
|
|
if (str_cr != ENC_CODERANGE_UNKNOWN && ptr_cr == ENC_CODERANGE_UNKNOWN) {
|
|
ptr_cr = coderange_scan(ptr, len, rb_enc_from_index(ptr_encindex));
|
|
}
|
|
}
|
|
else {
|
|
str_enc = rb_enc_from_index(str_encindex);
|
|
ptr_enc = rb_enc_from_index(ptr_encindex);
|
|
if (!rb_enc_asciicompat(str_enc) || !rb_enc_asciicompat(ptr_enc)) {
|
|
if (len == 0)
|
|
return str;
|
|
if (RSTRING_LEN(str) == 0) {
|
|
rb_str_buf_cat(str, ptr, len);
|
|
ENCODING_CODERANGE_SET(str, ptr_encindex, ptr_cr);
|
|
return str;
|
|
}
|
|
goto incompatible;
|
|
}
|
|
if (ptr_cr == ENC_CODERANGE_UNKNOWN) {
|
|
ptr_cr = coderange_scan(ptr, len, ptr_enc);
|
|
}
|
|
if (str_cr == ENC_CODERANGE_UNKNOWN) {
|
|
if (ENCODING_IS_ASCII8BIT(str) || ptr_cr != ENC_CODERANGE_7BIT) {
|
|
str_cr = rb_enc_str_coderange(str);
|
|
}
|
|
}
|
|
}
|
|
if (ptr_cr_ret)
|
|
*ptr_cr_ret = ptr_cr;
|
|
|
|
if (str_encindex != ptr_encindex &&
|
|
str_cr != ENC_CODERANGE_7BIT &&
|
|
ptr_cr != ENC_CODERANGE_7BIT) {
|
|
str_enc = rb_enc_from_index(str_encindex);
|
|
ptr_enc = rb_enc_from_index(ptr_encindex);
|
|
goto incompatible;
|
|
}
|
|
|
|
if (str_cr == ENC_CODERANGE_UNKNOWN) {
|
|
res_encindex = str_encindex;
|
|
res_cr = ENC_CODERANGE_UNKNOWN;
|
|
}
|
|
else if (str_cr == ENC_CODERANGE_7BIT) {
|
|
if (ptr_cr == ENC_CODERANGE_7BIT) {
|
|
res_encindex = str_encindex;
|
|
res_cr = ENC_CODERANGE_7BIT;
|
|
}
|
|
else {
|
|
res_encindex = ptr_encindex;
|
|
res_cr = ptr_cr;
|
|
}
|
|
}
|
|
else if (str_cr == ENC_CODERANGE_VALID) {
|
|
res_encindex = str_encindex;
|
|
if (ENC_CODERANGE_CLEAN_P(ptr_cr))
|
|
res_cr = str_cr;
|
|
else
|
|
res_cr = ptr_cr;
|
|
}
|
|
else { /* str_cr == ENC_CODERANGE_BROKEN */
|
|
res_encindex = str_encindex;
|
|
res_cr = str_cr;
|
|
if (0 < len) res_cr = ENC_CODERANGE_UNKNOWN;
|
|
}
|
|
|
|
if (len < 0) {
|
|
rb_raise(rb_eArgError, "negative string size (or size too big)");
|
|
}
|
|
str_buf_cat(str, ptr, len);
|
|
ENCODING_CODERANGE_SET(str, res_encindex, res_cr);
|
|
return str;
|
|
|
|
incompatible:
|
|
rb_raise(rb_eEncCompatError, "incompatible character encodings: %s and %s",
|
|
rb_enc_name(str_enc), rb_enc_name(ptr_enc));
|
|
UNREACHABLE_RETURN(Qundef);
|
|
}
|
|
|
|
VALUE
|
|
rb_enc_str_buf_cat(VALUE str, const char *ptr, long len, rb_encoding *ptr_enc)
|
|
{
|
|
return rb_enc_cr_str_buf_cat(str, ptr, len,
|
|
rb_enc_to_index(ptr_enc), ENC_CODERANGE_UNKNOWN, NULL);
|
|
}
|
|
|
|
VALUE
|
|
rb_str_buf_cat_ascii(VALUE str, const char *ptr)
|
|
{
|
|
/* ptr must reference NUL terminated ASCII string. */
|
|
int encindex = ENCODING_GET(str);
|
|
rb_encoding *enc = rb_enc_from_index(encindex);
|
|
if (rb_enc_asciicompat(enc)) {
|
|
return rb_enc_cr_str_buf_cat(str, ptr, strlen(ptr),
|
|
encindex, ENC_CODERANGE_7BIT, 0);
|
|
}
|
|
else {
|
|
char *buf = ALLOCA_N(char, rb_enc_mbmaxlen(enc));
|
|
while (*ptr) {
|
|
unsigned int c = (unsigned char)*ptr;
|
|
int len = rb_enc_codelen(c, enc);
|
|
rb_enc_mbcput(c, buf, enc);
|
|
rb_enc_cr_str_buf_cat(str, buf, len,
|
|
encindex, ENC_CODERANGE_VALID, 0);
|
|
ptr++;
|
|
}
|
|
return str;
|
|
}
|
|
}
|
|
|
|
VALUE
|
|
rb_str_buf_append(VALUE str, VALUE str2)
|
|
{
|
|
int str2_cr;
|
|
|
|
str2_cr = ENC_CODERANGE(str2);
|
|
|
|
rb_enc_cr_str_buf_cat(str, RSTRING_PTR(str2), RSTRING_LEN(str2),
|
|
ENCODING_GET(str2), str2_cr, &str2_cr);
|
|
|
|
ENC_CODERANGE_SET(str2, str2_cr);
|
|
|
|
return str;
|
|
}
|
|
|
|
VALUE
|
|
rb_str_append(VALUE str, VALUE str2)
|
|
{
|
|
StringValue(str2);
|
|
return rb_str_buf_append(str, str2);
|
|
}
|
|
|
|
#define MIN_PRE_ALLOC_SIZE 48
|
|
|
|
MJIT_FUNC_EXPORTED VALUE
|
|
rb_str_concat_literals(size_t num, const VALUE *strary)
|
|
{
|
|
VALUE str;
|
|
size_t i, s;
|
|
long len = 1;
|
|
|
|
if (UNLIKELY(!num)) return rb_str_new(0, 0);
|
|
if (UNLIKELY(num == 1)) return rb_str_resurrect(strary[0]);
|
|
|
|
for (i = 0; i < num; ++i) { len += RSTRING_LEN(strary[i]); }
|
|
if (LIKELY(len < MIN_PRE_ALLOC_SIZE)) {
|
|
str = rb_str_resurrect(strary[0]);
|
|
s = 1;
|
|
}
|
|
else {
|
|
str = rb_str_buf_new(len);
|
|
rb_enc_copy(str, strary[0]);
|
|
s = 0;
|
|
}
|
|
|
|
for (i = s; i < num; ++i) {
|
|
const VALUE v = strary[i];
|
|
int encidx = ENCODING_GET(v);
|
|
|
|
rb_enc_cr_str_buf_cat(str, RSTRING_PTR(v), RSTRING_LEN(v),
|
|
encidx, ENC_CODERANGE(v), NULL);
|
|
if (encidx != ENCINDEX_US_ASCII) {
|
|
if (ENCODING_GET_INLINED(str) == ENCINDEX_US_ASCII)
|
|
rb_enc_set_index(str, encidx);
|
|
}
|
|
}
|
|
return str;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* string.concat(*objects) -> new_string
|
|
*
|
|
* Returns a new \String containing the concatenation
|
|
* of +self+ and all objects in +objects+:
|
|
*
|
|
* s = 'foo'
|
|
* s.concat('bar', 'baz') # => "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.
|
|
*/
|
|
static VALUE
|
|
rb_str_concat_multi(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
str_modifiable(str);
|
|
|
|
if (argc == 1) {
|
|
return rb_str_concat(str, argv[0]);
|
|
}
|
|
else if (argc > 1) {
|
|
int i;
|
|
VALUE arg_str = rb_str_tmp_new(0);
|
|
rb_enc_copy(arg_str, str);
|
|
for (i = 0; i < argc; i++) {
|
|
rb_str_concat(arg_str, argv[i]);
|
|
}
|
|
rb_str_buf_append(str, arg_str);
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* string << object -> str
|
|
*
|
|
* Returns a new \String containing the concatenation
|
|
* of +self+ and +object+:
|
|
* s = 'foo'
|
|
* s << 'bar' # => "foobar"
|
|
*
|
|
* If +object+ is an \Integer,
|
|
* the value is considered a codepoint and converted to a character before concatenation:
|
|
* s = 'foo'
|
|
* s << 33 # => "foo!"
|
|
*
|
|
* Related: String#concat, which takes multiple arguments.
|
|
*/
|
|
VALUE
|
|
rb_str_concat(VALUE str1, VALUE str2)
|
|
{
|
|
unsigned int code;
|
|
rb_encoding *enc = STR_ENC_GET(str1);
|
|
int encidx;
|
|
|
|
if (RB_INTEGER_TYPE_P(str2)) {
|
|
if (rb_num_to_uint(str2, &code) == 0) {
|
|
}
|
|
else if (FIXNUM_P(str2)) {
|
|
rb_raise(rb_eRangeError, "%ld out of char range", FIX2LONG(str2));
|
|
}
|
|
else {
|
|
rb_raise(rb_eRangeError, "bignum out of char range");
|
|
}
|
|
}
|
|
else {
|
|
return rb_str_append(str1, str2);
|
|
}
|
|
|
|
encidx = rb_enc_to_index(enc);
|
|
if (encidx == ENCINDEX_ASCII || encidx == ENCINDEX_US_ASCII) {
|
|
/* US-ASCII automatically extended to ASCII-8BIT */
|
|
char buf[1];
|
|
buf[0] = (char)code;
|
|
if (code > 0xFF) {
|
|
rb_raise(rb_eRangeError, "%u out of char range", code);
|
|
}
|
|
rb_str_cat(str1, buf, 1);
|
|
if (encidx == ENCINDEX_US_ASCII && code > 127) {
|
|
rb_enc_associate_index(str1, ENCINDEX_ASCII);
|
|
ENC_CODERANGE_SET(str1, ENC_CODERANGE_VALID);
|
|
}
|
|
}
|
|
else {
|
|
long pos = RSTRING_LEN(str1);
|
|
int cr = ENC_CODERANGE(str1);
|
|
int len;
|
|
char *buf;
|
|
|
|
switch (len = rb_enc_codelen(code, enc)) {
|
|
case ONIGERR_INVALID_CODE_POINT_VALUE:
|
|
rb_raise(rb_eRangeError, "invalid codepoint 0x%X in %s", code, rb_enc_name(enc));
|
|
break;
|
|
case ONIGERR_TOO_BIG_WIDE_CHAR_VALUE:
|
|
case 0:
|
|
rb_raise(rb_eRangeError, "%u out of char range", code);
|
|
break;
|
|
}
|
|
buf = ALLOCA_N(char, len + 1);
|
|
rb_enc_mbcput(code, buf, enc);
|
|
if (rb_enc_precise_mbclen(buf, buf + len + 1, enc) != len) {
|
|
rb_raise(rb_eRangeError, "invalid codepoint 0x%X in %s", code, rb_enc_name(enc));
|
|
}
|
|
rb_str_resize(str1, pos+len);
|
|
memcpy(RSTRING_PTR(str1) + pos, buf, len);
|
|
if (cr == ENC_CODERANGE_7BIT && code > 127)
|
|
cr = ENC_CODERANGE_VALID;
|
|
ENC_CODERANGE_SET(str1, cr);
|
|
}
|
|
return str1;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* string.prepend(*other_strings) -> str
|
|
*
|
|
* Returns a new \String containing the concatenation
|
|
* of all given +other_strings+ and +self+:
|
|
* s = 'foo'
|
|
* s.prepend('bar', 'baz') # => "barbazfoo"
|
|
*
|
|
* Related: String#concat.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_prepend_multi(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
str_modifiable(str);
|
|
|
|
if (argc == 1) {
|
|
rb_str_update(str, 0L, 0L, argv[0]);
|
|
}
|
|
else if (argc > 1) {
|
|
int i;
|
|
VALUE arg_str = rb_str_tmp_new(0);
|
|
rb_enc_copy(arg_str, str);
|
|
for (i = 0; i < argc; i++) {
|
|
rb_str_append(arg_str, argv[i]);
|
|
}
|
|
rb_str_update(str, 0L, 0L, arg_str);
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
st_index_t
|
|
rb_str_hash(VALUE str)
|
|
{
|
|
int e = ENCODING_GET(str);
|
|
if (e && rb_enc_str_coderange(str) == ENC_CODERANGE_7BIT) {
|
|
e = 0;
|
|
}
|
|
return rb_memhash((const void *)RSTRING_PTR(str), RSTRING_LEN(str)) ^ e;
|
|
}
|
|
|
|
int
|
|
rb_str_hash_cmp(VALUE str1, VALUE str2)
|
|
{
|
|
long len1, len2;
|
|
const char *ptr1, *ptr2;
|
|
RSTRING_GETMEM(str1, ptr1, len1);
|
|
RSTRING_GETMEM(str2, ptr2, len2);
|
|
return (len1 != len2 ||
|
|
!rb_str_comparable(str1, str2) ||
|
|
memcmp(ptr1, ptr2, len1) != 0);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* string.hash -> integer
|
|
*
|
|
* Returns the integer hash value for +self+.
|
|
* The value is based on the length, content and encoding of +self+.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_hash_m(VALUE str)
|
|
{
|
|
st_index_t hval = rb_str_hash(str);
|
|
return ST2FIX(hval);
|
|
}
|
|
|
|
#define lesser(a,b) (((a)>(b))?(b):(a))
|
|
|
|
int
|
|
rb_str_comparable(VALUE str1, VALUE str2)
|
|
{
|
|
int idx1, idx2;
|
|
int rc1, rc2;
|
|
|
|
if (RSTRING_LEN(str1) == 0) return TRUE;
|
|
if (RSTRING_LEN(str2) == 0) return TRUE;
|
|
idx1 = ENCODING_GET(str1);
|
|
idx2 = ENCODING_GET(str2);
|
|
if (idx1 == idx2) return TRUE;
|
|
rc1 = rb_enc_str_coderange(str1);
|
|
rc2 = rb_enc_str_coderange(str2);
|
|
if (rc1 == ENC_CODERANGE_7BIT) {
|
|
if (rc2 == ENC_CODERANGE_7BIT) return TRUE;
|
|
if (rb_enc_asciicompat(rb_enc_from_index(idx2)))
|
|
return TRUE;
|
|
}
|
|
if (rc2 == ENC_CODERANGE_7BIT) {
|
|
if (rb_enc_asciicompat(rb_enc_from_index(idx1)))
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int
|
|
rb_str_cmp(VALUE str1, VALUE str2)
|
|
{
|
|
long len1, len2;
|
|
const char *ptr1, *ptr2;
|
|
int retval;
|
|
|
|
if (str1 == str2) return 0;
|
|
RSTRING_GETMEM(str1, ptr1, len1);
|
|
RSTRING_GETMEM(str2, ptr2, len2);
|
|
if (ptr1 == ptr2 || (retval = memcmp(ptr1, ptr2, lesser(len1, len2))) == 0) {
|
|
if (len1 == len2) {
|
|
if (!rb_str_comparable(str1, str2)) {
|
|
if (ENCODING_GET(str1) > ENCODING_GET(str2))
|
|
return 1;
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
if (len1 > len2) return 1;
|
|
return -1;
|
|
}
|
|
if (retval > 0) return 1;
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* string == object -> true or false
|
|
* string === object -> true or false
|
|
*
|
|
* Returns +true+ if +object+ has the same length and content;
|
|
* as +self+; +false+ otherwise:
|
|
* s = 'foo'
|
|
* s == 'foo' # => true
|
|
* s == 'food' # => 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>.
|
|
*/
|
|
|
|
VALUE
|
|
rb_str_equal(VALUE str1, VALUE str2)
|
|
{
|
|
if (str1 == str2) return Qtrue;
|
|
if (!RB_TYPE_P(str2, T_STRING)) {
|
|
if (!rb_respond_to(str2, idTo_str)) {
|
|
return Qfalse;
|
|
}
|
|
return rb_equal(str2, str1);
|
|
}
|
|
return rb_str_eql_internal(str1, str2);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* string.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
|
|
*/
|
|
|
|
MJIT_FUNC_EXPORTED VALUE
|
|
rb_str_eql(VALUE str1, VALUE str2)
|
|
{
|
|
if (str1 == str2) return Qtrue;
|
|
if (!RB_TYPE_P(str2, T_STRING)) return Qfalse;
|
|
return rb_str_eql_internal(str1, str2);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* string <=> other_string -> -1, 0, 1, or nil
|
|
*
|
|
* Compares +self+ and +other_string+, returning:
|
|
* - -1 if +other_string+ is smaller.
|
|
* - 0 if the two are equal.
|
|
* - 1 if +other_string+ 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
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_cmp_m(VALUE str1, VALUE str2)
|
|
{
|
|
int result;
|
|
VALUE s = rb_check_string_type(str2);
|
|
if (NIL_P(s)) {
|
|
return rb_invcmp(str1, str2);
|
|
}
|
|
result = rb_str_cmp(str1, s);
|
|
return INT2FIX(result);
|
|
}
|
|
|
|
static VALUE str_casecmp(VALUE str1, VALUE str2);
|
|
static VALUE str_casecmp_p(VALUE str1, VALUE str2);
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.casecmp(other_str) -> -1, 0, 1, or nil
|
|
*
|
|
* Compares +self+ and +other_string+, ignoring case, and returning:
|
|
* - -1 if +other_string+ is smaller.
|
|
* - 0 if the two are equal.
|
|
* - 1 if +other_string+ is larger.
|
|
* - +nil+ if the two are incomparable.
|
|
*
|
|
* Examples:
|
|
* 'foo'.casecmp('foo') # => 0
|
|
* 'foo'.casecmp('food') # => -1
|
|
* 'food'.casecmp('foo') # => 1
|
|
* 'FOO'.casecmp('foo') # => 0
|
|
* 'foo'.casecmp('FOO') # => 0
|
|
* 'foo'.casecmp(1) # => nil
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_casecmp(VALUE str1, VALUE str2)
|
|
{
|
|
VALUE s = rb_check_string_type(str2);
|
|
if (NIL_P(s)) {
|
|
return Qnil;
|
|
}
|
|
return str_casecmp(str1, s);
|
|
}
|
|
|
|
static VALUE
|
|
str_casecmp(VALUE str1, VALUE str2)
|
|
{
|
|
long len;
|
|
rb_encoding *enc;
|
|
const char *p1, *p1end, *p2, *p2end;
|
|
|
|
enc = rb_enc_compatible(str1, str2);
|
|
if (!enc) {
|
|
return Qnil;
|
|
}
|
|
|
|
p1 = RSTRING_PTR(str1); p1end = RSTRING_END(str1);
|
|
p2 = RSTRING_PTR(str2); p2end = RSTRING_END(str2);
|
|
if (single_byte_optimizable(str1) && single_byte_optimizable(str2)) {
|
|
while (p1 < p1end && p2 < p2end) {
|
|
if (*p1 != *p2) {
|
|
unsigned int c1 = TOLOWER(*p1 & 0xff);
|
|
unsigned int c2 = TOLOWER(*p2 & 0xff);
|
|
if (c1 != c2)
|
|
return INT2FIX(c1 < c2 ? -1 : 1);
|
|
}
|
|
p1++;
|
|
p2++;
|
|
}
|
|
}
|
|
else {
|
|
while (p1 < p1end && p2 < p2end) {
|
|
int l1, c1 = rb_enc_ascget(p1, p1end, &l1, enc);
|
|
int l2, c2 = rb_enc_ascget(p2, p2end, &l2, enc);
|
|
|
|
if (0 <= c1 && 0 <= c2) {
|
|
c1 = TOLOWER(c1);
|
|
c2 = TOLOWER(c2);
|
|
if (c1 != c2)
|
|
return INT2FIX(c1 < c2 ? -1 : 1);
|
|
}
|
|
else {
|
|
int r;
|
|
l1 = rb_enc_mbclen(p1, p1end, enc);
|
|
l2 = rb_enc_mbclen(p2, p2end, enc);
|
|
len = l1 < l2 ? l1 : l2;
|
|
r = memcmp(p1, p2, len);
|
|
if (r != 0)
|
|
return INT2FIX(r < 0 ? -1 : 1);
|
|
if (l1 != l2)
|
|
return INT2FIX(l1 < l2 ? -1 : 1);
|
|
}
|
|
p1 += l1;
|
|
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);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* string.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
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_casecmp_p(VALUE str1, VALUE str2)
|
|
{
|
|
VALUE s = rb_check_string_type(str2);
|
|
if (NIL_P(s)) {
|
|
return Qnil;
|
|
}
|
|
return str_casecmp_p(str1, s);
|
|
}
|
|
|
|
static VALUE
|
|
str_casecmp_p(VALUE str1, VALUE str2)
|
|
{
|
|
rb_encoding *enc;
|
|
VALUE folded_str1, folded_str2;
|
|
VALUE fold_opt = sym_fold;
|
|
|
|
enc = rb_enc_compatible(str1, str2);
|
|
if (!enc) {
|
|
return Qnil;
|
|
}
|
|
|
|
folded_str1 = rb_str_downcase(1, &fold_opt, str1);
|
|
folded_str2 = rb_str_downcase(1, &fold_opt, str2);
|
|
|
|
return rb_str_eql(folded_str1, folded_str2);
|
|
}
|
|
|
|
static long
|
|
strseq_core(const char *str_ptr, const char *str_ptr_end, long str_len,
|
|
const char *sub_ptr, long sub_len, long offset, rb_encoding *enc)
|
|
{
|
|
const char *search_start = str_ptr;
|
|
long pos, search_len = str_len - offset;
|
|
|
|
for (;;) {
|
|
const char *t;
|
|
pos = rb_memsearch(sub_ptr, sub_len, search_start, search_len, enc);
|
|
if (pos < 0) return pos;
|
|
t = rb_enc_right_char_head(search_start, search_start+pos, str_ptr_end, enc);
|
|
if (t == search_start + pos) break;
|
|
search_len -= t - search_start;
|
|
if (search_len <= 0) return -1;
|
|
offset += t - search_start;
|
|
search_start = t;
|
|
}
|
|
return pos + offset;
|
|
}
|
|
|
|
#define rb_str_index(str, sub, offset) rb_strseq_index(str, sub, offset, 0)
|
|
|
|
static long
|
|
rb_strseq_index(VALUE str, VALUE sub, long offset, int in_byte)
|
|
{
|
|
const char *str_ptr, *str_ptr_end, *sub_ptr;
|
|
long str_len, sub_len;
|
|
rb_encoding *enc;
|
|
|
|
enc = rb_enc_check(str, sub);
|
|
if (is_broken_string(sub)) return -1;
|
|
|
|
str_ptr = RSTRING_PTR(str);
|
|
str_ptr_end = RSTRING_END(str);
|
|
str_len = RSTRING_LEN(str);
|
|
sub_ptr = RSTRING_PTR(sub);
|
|
sub_len = RSTRING_LEN(sub);
|
|
|
|
if (str_len < sub_len) return -1;
|
|
|
|
if (offset != 0) {
|
|
long str_len_char, sub_len_char;
|
|
int single_byte = single_byte_optimizable(str);
|
|
str_len_char = (in_byte || single_byte) ? str_len : str_strlen(str, enc);
|
|
sub_len_char = in_byte ? sub_len : str_strlen(sub, enc);
|
|
if (offset < 0) {
|
|
offset += str_len_char;
|
|
if (offset < 0) return -1;
|
|
}
|
|
if (str_len_char - offset < sub_len_char) return -1;
|
|
if (!in_byte) offset = str_offset(str_ptr, str_ptr_end, offset, enc, single_byte);
|
|
str_ptr += offset;
|
|
}
|
|
if (sub_len == 0) return offset;
|
|
|
|
/* need proceed one character at a time */
|
|
return strseq_core(str_ptr, str_ptr_end, str_len, sub_ptr, sub_len, offset, enc);
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* string.index(substring, offset = 0) -> integer or nil
|
|
* string.index(regexp, offset = 0) -> integer or nil
|
|
*
|
|
* Returns the \Integer index of the first occurrence of the given +substring+,
|
|
* or +nil+ if none found:
|
|
* 'foo'.index('f') # => 0
|
|
* 'foo'.index('o') # => 1
|
|
* 'foo'.index('oo') # => 1
|
|
* 'foo'.index('ooo') # => nil
|
|
*
|
|
* Returns the \Integer index of the first match for the given \Regexp +regexp+,
|
|
* or +nil+ if none found:
|
|
* 'foo'.index(/f/) # => 0
|
|
* 'foo'.index(/o/) # => 1
|
|
* 'foo'.index(/oo/) # => 1
|
|
* 'foo'.index(/ooo/) # => nil
|
|
*
|
|
* \Integer argument +offset+, if given, specifies the position in the
|
|
* string to begin the search:
|
|
* 'foo'.index('o', 1) # => 1
|
|
* 'foo'.index('o', 2) # => 2
|
|
* 'foo'.index('o', 3) # => nil
|
|
*
|
|
* If +offset+ is negative, counts backward from the end of +self+:
|
|
* 'foo'.index('o', -1) # => 2
|
|
* 'foo'.index('o', -2) # => 1
|
|
* 'foo'.index('o', -3) # => 1
|
|
* 'foo'.index('o', -4) # => nil
|
|
*
|
|
* Related: String#rindex
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_index_m(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
VALUE sub;
|
|
VALUE initpos;
|
|
long pos;
|
|
|
|
if (rb_scan_args(argc, argv, "11", &sub, &initpos) == 2) {
|
|
pos = NUM2LONG(initpos);
|
|
}
|
|
else {
|
|
pos = 0;
|
|
}
|
|
if (pos < 0) {
|
|
pos += str_strlen(str, NULL);
|
|
if (pos < 0) {
|
|
if (RB_TYPE_P(sub, T_REGEXP)) {
|
|
rb_backref_set(Qnil);
|
|
}
|
|
return Qnil;
|
|
}
|
|
}
|
|
|
|
if (RB_TYPE_P(sub, T_REGEXP)) {
|
|
if (pos > str_strlen(str, NULL))
|
|
return Qnil;
|
|
pos = str_offset(RSTRING_PTR(str), RSTRING_END(str), pos,
|
|
rb_enc_check(str, sub), single_byte_optimizable(str));
|
|
|
|
if (rb_reg_search(sub, str, pos, 0) < 0) {
|
|
return Qnil;
|
|
} else {
|
|
VALUE match = rb_backref_get();
|
|
struct re_registers *regs = RMATCH_REGS(match);
|
|
pos = rb_str_sublen(str, BEG(0));
|
|
return LONG2NUM(pos);
|
|
}
|
|
}
|
|
else {
|
|
StringValue(sub);
|
|
pos = rb_str_index(str, sub, pos);
|
|
pos = rb_str_sublen(str, pos);
|
|
}
|
|
|
|
if (pos == -1) return Qnil;
|
|
return LONG2NUM(pos);
|
|
}
|
|
|
|
#ifdef HAVE_MEMRCHR
|
|
static long
|
|
str_rindex(VALUE str, VALUE sub, const char *s, long pos, rb_encoding *enc)
|
|
{
|
|
char *hit, *adjusted;
|
|
int c;
|
|
long slen, searchlen;
|
|
char *sbeg, *e, *t;
|
|
|
|
slen = RSTRING_LEN(sub);
|
|
if (slen == 0) return pos;
|
|
sbeg = RSTRING_PTR(str);
|
|
e = RSTRING_END(str);
|
|
t = RSTRING_PTR(sub);
|
|
c = *t & 0xff;
|
|
searchlen = s - sbeg + 1;
|
|
|
|
do {
|
|
hit = memrchr(sbeg, c, searchlen);
|
|
if (!hit) break;
|
|
adjusted = rb_enc_left_char_head(sbeg, hit, e, enc);
|
|
if (hit != adjusted) {
|
|
searchlen = adjusted - sbeg;
|
|
continue;
|
|
}
|
|
if (memcmp(hit, t, slen) == 0)
|
|
return rb_str_sublen(str, hit - sbeg);
|
|
searchlen = adjusted - sbeg;
|
|
} while (searchlen > 0);
|
|
|
|
return -1;
|
|
}
|
|
#else
|
|
static long
|
|
str_rindex(VALUE str, VALUE sub, const char *s, long pos, 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 pos;
|
|
}
|
|
if (pos == 0) break;
|
|
pos--;
|
|
s = rb_enc_prev_char(sbeg, s, e, enc);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
static long
|
|
rb_str_rindex(VALUE str, VALUE sub, long pos)
|
|
{
|
|
long len, slen;
|
|
char *sbeg, *s;
|
|
rb_encoding *enc;
|
|
int singlebyte;
|
|
|
|
enc = rb_enc_check(str, sub);
|
|
if (is_broken_string(sub)) return -1;
|
|
singlebyte = single_byte_optimizable(str);
|
|
len = singlebyte ? RSTRING_LEN(str) : str_strlen(str, enc); /* rb_enc_check */
|
|
slen = str_strlen(sub, enc); /* rb_enc_check */
|
|
|
|
/* substring longer than string */
|
|
if (len < slen) return -1;
|
|
if (len - pos < slen) pos = len - slen;
|
|
if (len == 0) return pos;
|
|
|
|
sbeg = RSTRING_PTR(str);
|
|
|
|
if (pos == 0) {
|
|
if (memcmp(sbeg, RSTRING_PTR(sub), RSTRING_LEN(sub)) == 0)
|
|
return 0;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
s = str_nth(sbeg, RSTRING_END(str), pos, enc, singlebyte);
|
|
return str_rindex(str, sub, s, pos, enc);
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* string.rindex(substring, offset = self.length) -> integer or nil
|
|
* string.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
|
|
*
|
|
* \Integer argument +offset+, if given and non-negative, specifies the maximum starting position in the
|
|
* string to _end_ the search:
|
|
* 'foo'.rindex('o', 0) # => nil
|
|
* 'foo'.rindex('o', 1) # => 1
|
|
* 'foo'.rindex('o', 2) # => 2
|
|
* 'foo'.rindex('o', 3) # => 2
|
|
*
|
|
* If +offset+ is a negative \Integer, the maximum starting position in the
|
|
* string to _end_ the search is the sum of the string's length and +offset+:
|
|
* 'foo'.rindex('o', -1) # => 2
|
|
* 'foo'.rindex('o', -2) # => 1
|
|
* 'foo'.rindex('o', -3) # => nil
|
|
* 'foo'.rindex('o', -4) # => nil
|
|
*
|
|
* Related: String#index
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_rindex_m(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
VALUE sub;
|
|
VALUE vpos;
|
|
rb_encoding *enc = STR_ENC_GET(str);
|
|
long pos, len = str_strlen(str, enc); /* str's enc */
|
|
|
|
if (rb_scan_args(argc, argv, "11", &sub, &vpos) == 2) {
|
|
pos = NUM2LONG(vpos);
|
|
if (pos < 0) {
|
|
pos += len;
|
|
if (pos < 0) {
|
|
if (RB_TYPE_P(sub, T_REGEXP)) {
|
|
rb_backref_set(Qnil);
|
|
}
|
|
return Qnil;
|
|
}
|
|
}
|
|
if (pos > len) pos = len;
|
|
}
|
|
else {
|
|
pos = len;
|
|
}
|
|
|
|
if (RB_TYPE_P(sub, T_REGEXP)) {
|
|
/* enc = rb_get_check(str, sub); */
|
|
pos = str_offset(RSTRING_PTR(str), RSTRING_END(str), pos,
|
|
enc, single_byte_optimizable(str));
|
|
|
|
if (rb_reg_search(sub, str, pos, 1) >= 0) {
|
|
VALUE match = rb_backref_get();
|
|
struct re_registers *regs = RMATCH_REGS(match);
|
|
pos = rb_str_sublen(str, BEG(0));
|
|
return LONG2NUM(pos);
|
|
}
|
|
}
|
|
else {
|
|
StringValue(sub);
|
|
pos = rb_str_rindex(str, sub, pos);
|
|
if (pos >= 0) return LONG2NUM(pos);
|
|
}
|
|
return Qnil;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* string =~ regexp -> integer or nil
|
|
* string =~ object -> integer or nil
|
|
*
|
|
* Returns the \Integer index of the first substring that matches
|
|
* the given +regexp+, or +nil+ if no match found:
|
|
* 'foo' =~ /f/ # => 0
|
|
* 'foo' =~ /o/ # => 1
|
|
* 'foo' =~ /x/ # => nil
|
|
*
|
|
* Note: also updates
|
|
* {Regexp-related global variables}[Regexp.html#class-Regexp-label-Special+global+variables].
|
|
*
|
|
* If the given +object+ is not a \Regexp, returns the value
|
|
* returned by <tt>object =~ self</tt>.
|
|
*
|
|
* Note that <tt>string =~ regexp</tt> is different from <tt>regexp =~ string</tt>
|
|
* (see {Regexp#=~}[https://ruby-doc.org/core-2.7.1/Regexp.html#method-i-3D-7E]):
|
|
* number= nil
|
|
* "no. 9" =~ /(?<number>\d+)/
|
|
* number # => nil (not assigned)
|
|
* /(?<number>\d+)/ =~ "no. 9"
|
|
* number #=> "9"
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_match(VALUE x, VALUE y)
|
|
{
|
|
switch (OBJ_BUILTIN_TYPE(y)) {
|
|
case T_STRING:
|
|
rb_raise(rb_eTypeError, "type mismatch: String given");
|
|
|
|
case T_REGEXP:
|
|
return rb_reg_match(y, x);
|
|
|
|
default:
|
|
return rb_funcall(y, idEqTilde, 1, x);
|
|
}
|
|
}
|
|
|
|
|
|
static VALUE get_pat(VALUE);
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* string.match(pattern, offset = 0) -> matchdata or nil
|
|
* string.match(pattern, offset = 0) {|matchdata| ... } -> object
|
|
*
|
|
* Returns a \Matchdata object (or +nil+) based on +self+ and the given +pattern+.
|
|
*
|
|
* Note: also updates
|
|
* {Regexp-related global variables}[Regexp.html#class-Regexp-label-Special+global+variables].
|
|
*
|
|
* - Computes +regexp+ by converting +pattern+ (if not already a \Regexp).
|
|
* regexp = Regexp.new(pattern)
|
|
* - Computes +matchdata+, which will be either a \MatchData object or +nil+
|
|
* (see Regexp#match):
|
|
* matchdata = <tt>regexp.match(self)
|
|
*
|
|
* With no block given, returns the computed +matchdata+:
|
|
* 'foo'.match('f') # => #<MatchData "f">
|
|
* 'foo'.match('o') # => #<MatchData "o">
|
|
* 'foo'.match('x') # => nil
|
|
*
|
|
* If \Integer argument +offset+ is given, the search begins at index +offset+:
|
|
* '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:
|
|
* 'foo'.match(/o/) {|matchdata| matchdata } # => #<MatchData "o">
|
|
* 'foo'.match(/x/) {|matchdata| matchdata } # => nil
|
|
* 'foo'.match(/f/, 1) {|matchdata| matchdata } # => nil
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_match_m(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
VALUE re, result;
|
|
if (argc < 1)
|
|
rb_check_arity(argc, 1, 2);
|
|
re = argv[0];
|
|
argv[0] = str;
|
|
result = rb_funcallv(get_pat(re), rb_intern("match"), argc, argv);
|
|
if (!NIL_P(result) && rb_block_given_p()) {
|
|
return rb_yield(result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* string.match?(pattern, offset = 0) -> true or false
|
|
*
|
|
* Returns +true+ or +false+ based on whether a match is found for +self+ and +pattern+.
|
|
*
|
|
* Note: does not update
|
|
* {Regexp-related global variables}[Regexp.html#class-Regexp-label-Special+global+variables].
|
|
*
|
|
* 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,
|
|
* +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
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_match_m_p(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
VALUE re;
|
|
rb_check_arity(argc, 1, 2);
|
|
re = get_pat(argv[0]);
|
|
return rb_reg_match_p(re, str, argc > 1 ? NUM2LONG(argv[1]) : 0);
|
|
}
|
|
|
|
enum neighbor_char {
|
|
NEIGHBOR_NOT_CHAR,
|
|
NEIGHBOR_FOUND,
|
|
NEIGHBOR_WRAPPED
|
|
};
|
|
|
|
static enum neighbor_char
|
|
enc_succ_char(char *p, long len, rb_encoding *enc)
|
|
{
|
|
long i;
|
|
int l;
|
|
|
|
if (rb_enc_mbminlen(enc) > 1) {
|
|
/* wchar, trivial case */
|
|
int r = rb_enc_precise_mbclen(p, p + len, enc), c;
|
|
if (!MBCLEN_CHARFOUND_P(r)) {
|
|
return NEIGHBOR_NOT_CHAR;
|
|
}
|
|
c = rb_enc_mbc_to_codepoint(p, p + len, enc) + 1;
|
|
l = rb_enc_code_to_mbclen(c, enc);
|
|
if (!l) return NEIGHBOR_NOT_CHAR;
|
|
if (l != len) return NEIGHBOR_WRAPPED;
|
|
rb_enc_mbcput(c, p, enc);
|
|
r = rb_enc_precise_mbclen(p, p + len, enc);
|
|
if (!MBCLEN_CHARFOUND_P(r)) {
|
|
return NEIGHBOR_NOT_CHAR;
|
|
}
|
|
return NEIGHBOR_FOUND;
|
|
}
|
|
while (1) {
|
|
for (i = len-1; 0 <= i && (unsigned char)p[i] == 0xff; i--)
|
|
p[i] = '\0';
|
|
if (i < 0)
|
|
return NEIGHBOR_WRAPPED;
|
|
++((unsigned char*)p)[i];
|
|
l = rb_enc_precise_mbclen(p, p+len, enc);
|
|
if (MBCLEN_CHARFOUND_P(l)) {
|
|
l = MBCLEN_CHARFOUND_LEN(l);
|
|
if (l == len) {
|
|
return NEIGHBOR_FOUND;
|
|
}
|
|
else {
|
|
memset(p+l, 0xff, len-l);
|
|
}
|
|
}
|
|
if (MBCLEN_INVALID_P(l) && i < len-1) {
|
|
long len2;
|
|
int l2;
|
|
for (len2 = len-1; 0 < len2; len2--) {
|
|
l2 = rb_enc_precise_mbclen(p, p+len2, enc);
|
|
if (!MBCLEN_INVALID_P(l2))
|
|
break;
|
|
}
|
|
memset(p+len2+1, 0xff, len-(len2+1));
|
|
}
|
|
}
|
|
}
|
|
|
|
static enum neighbor_char
|
|
enc_pred_char(char *p, long len, rb_encoding *enc)
|
|
{
|
|
long i;
|
|
int l;
|
|
if (rb_enc_mbminlen(enc) > 1) {
|
|
/* wchar, trivial case */
|
|
int r = rb_enc_precise_mbclen(p, p + len, enc), c;
|
|
if (!MBCLEN_CHARFOUND_P(r)) {
|
|
return NEIGHBOR_NOT_CHAR;
|
|
}
|
|
c = rb_enc_mbc_to_codepoint(p, p + len, enc);
|
|
if (!c) return NEIGHBOR_NOT_CHAR;
|
|
--c;
|
|
l = rb_enc_code_to_mbclen(c, enc);
|
|
if (!l) return NEIGHBOR_NOT_CHAR;
|
|
if (l != len) return NEIGHBOR_WRAPPED;
|
|
rb_enc_mbcput(c, p, enc);
|
|
r = rb_enc_precise_mbclen(p, p + len, enc);
|
|
if (!MBCLEN_CHARFOUND_P(r)) {
|
|
return NEIGHBOR_NOT_CHAR;
|
|
}
|
|
return NEIGHBOR_FOUND;
|
|
}
|
|
while (1) {
|
|
for (i = len-1; 0 <= i && (unsigned char)p[i] == 0; i--)
|
|
p[i] = '\xff';
|
|
if (i < 0)
|
|
return NEIGHBOR_WRAPPED;
|
|
--((unsigned char*)p)[i];
|
|
l = rb_enc_precise_mbclen(p, p+len, enc);
|
|
if (MBCLEN_CHARFOUND_P(l)) {
|
|
l = MBCLEN_CHARFOUND_LEN(l);
|
|
if (l == len) {
|
|
return NEIGHBOR_FOUND;
|
|
}
|
|
else {
|
|
memset(p+l, 0, len-l);
|
|
}
|
|
}
|
|
if (MBCLEN_INVALID_P(l) && i < len-1) {
|
|
long len2;
|
|
int l2;
|
|
for (len2 = len-1; 0 < len2; len2--) {
|
|
l2 = rb_enc_precise_mbclen(p, p+len2, enc);
|
|
if (!MBCLEN_INVALID_P(l2))
|
|
break;
|
|
}
|
|
memset(p+len2+1, 0, len-(len2+1));
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
overwrite +p+ by succeeding letter in +enc+ and returns
|
|
NEIGHBOR_FOUND or NEIGHBOR_WRAPPED.
|
|
When NEIGHBOR_WRAPPED, carried-out letter is stored into carry.
|
|
assuming each ranges are successive, and mbclen
|
|
never change in each ranges.
|
|
NEIGHBOR_NOT_CHAR is returned if invalid character or the range has only one
|
|
character.
|
|
*/
|
|
static enum neighbor_char
|
|
enc_succ_alnum_char(char *p, long len, rb_encoding *enc, char *carry)
|
|
{
|
|
enum neighbor_char ret;
|
|
unsigned int c;
|
|
int ctype;
|
|
int range;
|
|
char save[ONIGENC_CODE_TO_MBC_MAXLEN];
|
|
|
|
/* skip 03A2, invalid char between GREEK CAPITAL LETTERS */
|
|
int try;
|
|
const int max_gaps = 1;
|
|
|
|
c = rb_enc_mbc_to_codepoint(p, p+len, enc);
|
|
if (rb_enc_isctype(c, ONIGENC_CTYPE_DIGIT, enc))
|
|
ctype = ONIGENC_CTYPE_DIGIT;
|
|
else if (rb_enc_isctype(c, ONIGENC_CTYPE_ALPHA, enc))
|
|
ctype = ONIGENC_CTYPE_ALPHA;
|
|
else
|
|
return NEIGHBOR_NOT_CHAR;
|
|
|
|
MEMCPY(save, p, char, len);
|
|
for (try = 0; try <= max_gaps; ++try) {
|
|
ret = enc_succ_char(p, len, enc);
|
|
if (ret == NEIGHBOR_FOUND) {
|
|
c = rb_enc_mbc_to_codepoint(p, p+len, enc);
|
|
if (rb_enc_isctype(c, ctype, enc))
|
|
return NEIGHBOR_FOUND;
|
|
}
|
|
}
|
|
MEMCPY(p, save, char, len);
|
|
range = 1;
|
|
while (1) {
|
|
MEMCPY(save, p, char, len);
|
|
ret = enc_pred_char(p, len, enc);
|
|
if (ret == NEIGHBOR_FOUND) {
|
|
c = rb_enc_mbc_to_codepoint(p, p+len, enc);
|
|
if (!rb_enc_isctype(c, ctype, enc)) {
|
|
MEMCPY(p, save, char, len);
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
MEMCPY(p, save, char, len);
|
|
break;
|
|
}
|
|
range++;
|
|
}
|
|
if (range == 1) {
|
|
return NEIGHBOR_NOT_CHAR;
|
|
}
|
|
|
|
if (ctype != ONIGENC_CTYPE_DIGIT) {
|
|
MEMCPY(carry, p, char, len);
|
|
return NEIGHBOR_WRAPPED;
|
|
}
|
|
|
|
MEMCPY(carry, p, char, len);
|
|
enc_succ_char(carry, len, enc);
|
|
return NEIGHBOR_WRAPPED;
|
|
}
|
|
|
|
|
|
static VALUE str_succ(VALUE str);
|
|
|
|
/*
|
|
* call-seq:
|
|
* string.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 # => ""
|
|
*
|
|
* String#next is an alias for String#succ.
|
|
*/
|
|
|
|
VALUE
|
|
rb_str_succ(VALUE orig)
|
|
{
|
|
VALUE str;
|
|
str = rb_str_new(RSTRING_PTR(orig), RSTRING_LEN(orig));
|
|
rb_enc_cr_str_copy_for_substr(str, orig);
|
|
return str_succ(str);
|
|
}
|
|
|
|
static VALUE
|
|
str_succ(VALUE str)
|
|
{
|
|
rb_encoding *enc;
|
|
char *sbeg, *s, *e, *last_alnum = 0;
|
|
int found_alnum = 0;
|
|
long l, slen;
|
|
char carry[ONIGENC_CODE_TO_MBC_MAXLEN] = "\1";
|
|
long carry_pos = 0, carry_len = 1;
|
|
enum neighbor_char neighbor = NEIGHBOR_FOUND;
|
|
|
|
slen = RSTRING_LEN(str);
|
|
if (slen == 0) return str;
|
|
|
|
enc = STR_ENC_GET(str);
|
|
sbeg = RSTRING_PTR(str);
|
|
s = e = sbeg + slen;
|
|
|
|
while ((s = rb_enc_prev_char(sbeg, s, e, enc)) != 0) {
|
|
if (neighbor == NEIGHBOR_NOT_CHAR && last_alnum) {
|
|
if (ISALPHA(*last_alnum) ? ISDIGIT(*s) :
|
|
ISDIGIT(*last_alnum) ? ISALPHA(*s) : 0) {
|
|
break;
|
|
}
|
|
}
|
|
l = rb_enc_precise_mbclen(s, e, enc);
|
|
if (!ONIGENC_MBCLEN_CHARFOUND_P(l)) continue;
|
|
l = ONIGENC_MBCLEN_CHARFOUND_LEN(l);
|
|
neighbor = enc_succ_alnum_char(s, l, enc, carry);
|
|
switch (neighbor) {
|
|
case NEIGHBOR_NOT_CHAR:
|
|
continue;
|
|
case NEIGHBOR_FOUND:
|
|
return str;
|
|
case NEIGHBOR_WRAPPED:
|
|
last_alnum = s;
|
|
break;
|
|
}
|
|
found_alnum = 1;
|
|
carry_pos = s - sbeg;
|
|
carry_len = l;
|
|
}
|
|
if (!found_alnum) { /* str contains no alnum */
|
|
s = e;
|
|
while ((s = rb_enc_prev_char(sbeg, s, e, enc)) != 0) {
|
|
enum neighbor_char neighbor;
|
|
char tmp[ONIGENC_CODE_TO_MBC_MAXLEN];
|
|
l = rb_enc_precise_mbclen(s, e, enc);
|
|
if (!ONIGENC_MBCLEN_CHARFOUND_P(l)) continue;
|
|
l = ONIGENC_MBCLEN_CHARFOUND_LEN(l);
|
|
MEMCPY(tmp, s, char, l);
|
|
neighbor = enc_succ_char(tmp, l, enc);
|
|
switch (neighbor) {
|
|
case NEIGHBOR_FOUND:
|
|
MEMCPY(s, tmp, char, l);
|
|
return str;
|
|
break;
|
|
case NEIGHBOR_WRAPPED:
|
|
MEMCPY(s, tmp, char, l);
|
|
break;
|
|
case NEIGHBOR_NOT_CHAR:
|
|
break;
|
|
}
|
|
if (rb_enc_precise_mbclen(s, s+l, enc) != l) {
|
|
/* wrapped to \0...\0. search next valid char. */
|
|
enc_succ_char(s, l, enc);
|
|
}
|
|
if (!rb_enc_asciicompat(enc)) {
|
|
MEMCPY(carry, s, char, l);
|
|
carry_len = l;
|
|
}
|
|
carry_pos = s - sbeg;
|
|
}
|
|
ENC_CODERANGE_SET(str, ENC_CODERANGE_UNKNOWN);
|
|
}
|
|
RESIZE_CAPA(str, slen + carry_len);
|
|
sbeg = RSTRING_PTR(str);
|
|
s = sbeg + carry_pos;
|
|
memmove(s + carry_len, s, slen - carry_pos);
|
|
memmove(s, carry, carry_len);
|
|
slen += carry_len;
|
|
STR_SET_LEN(str, slen);
|
|
TERM_FILL(&sbeg[slen], rb_enc_mbminlen(enc));
|
|
rb_enc_str_coderange(str);
|
|
return str;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* string.succ! -> self
|
|
*
|
|
* Equivalent to String#succ, but modifies +self+ in place; returns +self+.
|
|
*
|
|
* String#next! is an alias for String#succ!.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_succ_bang(VALUE str)
|
|
{
|
|
rb_str_modify(str);
|
|
str_succ(str);
|
|
return str;
|
|
}
|
|
|
|
static int
|
|
all_digits_p(const char *s, long len)
|
|
{
|
|
while (len-- > 0) {
|
|
if (!ISDIGIT(*s)) return 0;
|
|
s++;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
str_upto_i(VALUE str, VALUE arg)
|
|
{
|
|
rb_yield(str);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* string.upto(other_string, exclusive = false) {|string| ... } -> self
|
|
* string.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")>
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_upto(int argc, VALUE *argv, VALUE beg)
|
|
{
|
|
VALUE end, exclusive;
|
|
|
|
rb_scan_args(argc, argv, "11", &end, &exclusive);
|
|
RETURN_ENUMERATOR(beg, argc, argv);
|
|
return rb_str_upto_each(beg, end, RTEST(exclusive), str_upto_i, Qnil);
|
|
}
|
|
|
|
VALUE
|
|
rb_str_upto_each(VALUE beg, VALUE end, int excl, int (*each)(VALUE, VALUE), VALUE arg)
|
|
{
|
|
VALUE current, after_end;
|
|
ID succ;
|
|
int n, ascii;
|
|
rb_encoding *enc;
|
|
|
|
CONST_ID(succ, "succ");
|
|
StringValue(end);
|
|
enc = rb_enc_check(beg, end);
|
|
ascii = (is_ascii_string(beg) && is_ascii_string(end));
|
|
/* single character */
|
|
if (RSTRING_LEN(beg) == 1 && RSTRING_LEN(end) == 1 && ascii) {
|
|
char c = RSTRING_PTR(beg)[0];
|
|
char e = RSTRING_PTR(end)[0];
|
|
|
|
if (c > e || (excl && c == e)) return beg;
|
|
for (;;) {
|
|
if ((*each)(rb_enc_str_new(&c, 1, enc), arg)) break;
|
|
if (!excl && c == e) break;
|
|
c++;
|
|
if (excl && c == e) break;
|
|
}
|
|
return beg;
|
|
}
|
|
/* both edges are all digits */
|
|
if (ascii && ISDIGIT(RSTRING_PTR(beg)[0]) && ISDIGIT(RSTRING_PTR(end)[0]) &&
|
|
all_digits_p(RSTRING_PTR(beg), RSTRING_LEN(beg)) &&
|
|
all_digits_p(RSTRING_PTR(end), RSTRING_LEN(end))) {
|
|
VALUE b, e;
|
|
int width;
|
|
|
|
width = RSTRING_LENINT(beg);
|
|
b = rb_str_to_inum(beg, 10, FALSE);
|
|
e = rb_str_to_inum(end, 10, FALSE);
|
|
if (FIXNUM_P(b) && FIXNUM_P(e)) {
|
|
long bi = FIX2LONG(b);
|
|
long ei = FIX2LONG(e);
|
|
rb_encoding *usascii = rb_usascii_encoding();
|
|
|
|
while (bi <= ei) {
|
|
if (excl && bi == ei) break;
|
|
if ((*each)(rb_enc_sprintf(usascii, "%.*ld", width, bi), arg)) break;
|
|
bi++;
|
|
}
|
|
}
|
|
else {
|
|
ID op = excl ? '<' : idLE;
|
|
VALUE args[2], fmt = rb_fstring_lit("%.*d");
|
|
|
|
args[0] = INT2FIX(width);
|
|
while (rb_funcall(b, op, 1, e)) {
|
|
args[1] = b;
|
|
if ((*each)(rb_str_format(numberof(args), args, fmt), arg)) break;
|
|
b = rb_funcallv(b, succ, 0, 0);
|
|
}
|
|
}
|
|
return beg;
|
|
}
|
|
/* normal case */
|
|
n = rb_str_cmp(beg, end);
|
|
if (n > 0 || (excl && n == 0)) return beg;
|
|
|
|
after_end = rb_funcallv(end, succ, 0, 0);
|
|
current = str_duplicate(rb_cString, beg);
|
|
while (!rb_str_equal(current, after_end)) {
|
|
VALUE next = Qnil;
|
|
if (excl || !rb_str_equal(current, end))
|
|
next = rb_funcallv(current, succ, 0, 0);
|
|
if ((*each)(current, arg)) break;
|
|
if (NIL_P(next)) break;
|
|
current = next;
|
|
StringValue(current);
|
|
if (excl && rb_str_equal(current, end)) break;
|
|
if (RSTRING_LEN(current) > RSTRING_LEN(end) || RSTRING_LEN(current) == 0)
|
|
break;
|
|
}
|
|
|
|
return beg;
|
|
}
|
|
|
|
VALUE
|
|
rb_str_upto_endless_each(VALUE beg, int (*each)(VALUE, VALUE), VALUE arg)
|
|
{
|
|
VALUE current;
|
|
ID succ;
|
|
|
|
CONST_ID(succ, "succ");
|
|
/* both edges are all digits */
|
|
if (is_ascii_string(beg) && ISDIGIT(RSTRING_PTR(beg)[0]) &&
|
|
all_digits_p(RSTRING_PTR(beg), RSTRING_LEN(beg))) {
|
|
VALUE b, args[2], fmt = rb_fstring_lit("%.*d");
|
|
int width = RSTRING_LENINT(beg);
|
|
b = rb_str_to_inum(beg, 10, FALSE);
|
|
if (FIXNUM_P(b)) {
|
|
long bi = FIX2LONG(b);
|
|
rb_encoding *usascii = rb_usascii_encoding();
|
|
|
|
while (FIXABLE(bi)) {
|
|
if ((*each)(rb_enc_sprintf(usascii, "%.*ld", width, bi), arg)) break;
|
|
bi++;
|
|
}
|
|
b = LONG2NUM(bi);
|
|
}
|
|
args[0] = INT2FIX(width);
|
|
while (1) {
|
|
args[1] = b;
|
|
if ((*each)(rb_str_format(numberof(args), args, fmt), arg)) break;
|
|
b = rb_funcallv(b, succ, 0, 0);
|
|
}
|
|
}
|
|
/* normal case */
|
|
current = str_duplicate(rb_cString, beg);
|
|
while (1) {
|
|
VALUE next = rb_funcallv(current, succ, 0, 0);
|
|
if ((*each)(current, arg)) break;
|
|
current = next;
|
|
StringValue(current);
|
|
if (RSTRING_LEN(current) == 0)
|
|
break;
|
|
}
|
|
|
|
return beg;
|
|
}
|
|
|
|
static int
|
|
include_range_i(VALUE str, VALUE arg)
|
|
{
|
|
VALUE *argp = (VALUE *)arg;
|
|
if (!rb_equal(str, *argp)) return 0;
|
|
*argp = Qnil;
|
|
return 1;
|
|
}
|
|
|
|
VALUE
|
|
rb_str_include_range_p(VALUE beg, VALUE end, VALUE val, VALUE exclusive)
|
|
{
|
|
beg = rb_str_new_frozen(beg);
|
|
StringValue(end);
|
|
end = rb_str_new_frozen(end);
|
|
if (NIL_P(val)) return Qfalse;
|
|
val = rb_check_string_type(val);
|
|
if (NIL_P(val)) return Qfalse;
|
|
if (rb_enc_asciicompat(STR_ENC_GET(beg)) &&
|
|
rb_enc_asciicompat(STR_ENC_GET(end)) &&
|
|
rb_enc_asciicompat(STR_ENC_GET(val))) {
|
|
const char *bp = RSTRING_PTR(beg);
|
|
const char *ep = RSTRING_PTR(end);
|
|
const char *vp = RSTRING_PTR(val);
|
|
if (RSTRING_LEN(beg) == 1 && RSTRING_LEN(end) == 1) {
|
|
if (RSTRING_LEN(val) == 0 || RSTRING_LEN(val) > 1)
|
|
return Qfalse;
|
|
else {
|
|
char b = *bp;
|
|
char e = *ep;
|
|
char v = *vp;
|
|
|
|
if (ISASCII(b) && ISASCII(e) && ISASCII(v)) {
|
|
if (b <= v && v < e) return Qtrue;
|
|
if (!RTEST(exclusive) && v == e) return Qtrue;
|
|
return Qfalse;
|
|
}
|
|
}
|
|
}
|
|
#if 0
|
|
/* both edges are all digits */
|
|
if (ISDIGIT(*bp) && ISDIGIT(*ep) &&
|
|
all_digits_p(bp, RSTRING_LEN(beg)) &&
|
|
all_digits_p(ep, RSTRING_LEN(end))) {
|
|
/* TODO */
|
|
}
|
|
#endif
|
|
}
|
|
rb_str_upto_each(beg, end, RTEST(exclusive), include_range_i, (VALUE)&val);
|
|
|
|
return NIL_P(val) ? Qtrue : Qfalse;
|
|
}
|
|
|
|
static VALUE
|
|
rb_str_subpat(VALUE str, VALUE re, VALUE backref)
|
|
{
|
|
if (rb_reg_search(re, str, 0, 0) >= 0) {
|
|
VALUE match = rb_backref_get();
|
|
int nth = rb_reg_backref_number(match, backref);
|
|
return rb_reg_nth_match(nth, match);
|
|
}
|
|
return Qnil;
|
|
}
|
|
|
|
static VALUE
|
|
rb_str_aref(VALUE str, VALUE indx)
|
|
{
|
|
long idx;
|
|
|
|
if (FIXNUM_P(indx)) {
|
|
idx = FIX2LONG(indx);
|
|
}
|
|
else if (RB_TYPE_P(indx, T_REGEXP)) {
|
|
return rb_str_subpat(str, indx, INT2FIX(0));
|
|
}
|
|
else if (RB_TYPE_P(indx, T_STRING)) {
|
|
if (rb_str_index(str, indx, 0) != -1)
|
|
return str_duplicate(rb_cString, indx);
|
|
return Qnil;
|
|
}
|
|
else {
|
|
/* check if indx is Range */
|
|
long beg, len = str_strlen(str, NULL);
|
|
switch (rb_range_beg_len(indx, &beg, &len, len, 0)) {
|
|
case Qfalse:
|
|
break;
|
|
case Qnil:
|
|
return Qnil;
|
|
default:
|
|
return rb_str_substr(str, beg, len);
|
|
}
|
|
idx = NUM2LONG(indx);
|
|
}
|
|
|
|
return str_substr(str, idx, 1, FALSE);
|
|
}
|
|
|
|
|
|
/*
|
|
* 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.
|
|
*
|
|
* When the single \Integer argument +index+ is given,
|
|
* returns the 1-character substring found in +self+ at offset +index+:
|
|
* 'bar'[2] # => "r"
|
|
* Counts backward from the end of +self+ if +index+ is negative:
|
|
* 'foo'[-3] # => "f"
|
|
* Returns +nil+ if +index+ is out of range:
|
|
* 'foo'[3] # => nil
|
|
* 'foo'[-4] # => nil
|
|
*
|
|
* When the two \Integer arguments +start+ and +length+ are given,
|
|
* returns the substring of the given +length+ found in +self+ at offset +start+:
|
|
* 'foo'[0, 2] # => "fo"
|
|
* 'foo'[0, 0] # => ""
|
|
* Counts backward from the end of +self+ if +start+ is negative:
|
|
* 'foo'[-2, 2] # => "oo"
|
|
* Special case: returns a new empty \String if +start+ is equal to the length of +self+:
|
|
* 'foo'[3, 2] # => ""
|
|
* Returns +nil+ if +start+ is out of range:
|
|
* 'foo'[4, 2] # => nil
|
|
* 'foo'[-4, 2] # => nil
|
|
* Returns the trailing substring of +self+ if +length+ is large:
|
|
* 'foo'[1, 50] # => "oo"
|
|
* Returns +nil+ if +length+ is negative:
|
|
* 'foo'[0, -1] # => nil
|
|
*
|
|
* When the single \Range argument +range+ is given,
|
|
* derives +start+ and +length+ values from the given +range+,
|
|
* and returns values as above:
|
|
* - <tt>'foo'[0..1]</tt> is equivalent to <tt>'foo'[0, 2]</tt>.
|
|
* - <tt>'foo'[0...1]</tt> is equivalent to <tt>'foo'[0, 1]</tt>.
|
|
*
|
|
* When the \Regexp argument +regexp+ is given,
|
|
* and the +capture+ argument is <tt>0</tt>,
|
|
* returns the first matching substring found in +self+,
|
|
* or +nil+ if none found:
|
|
* 'foo'[/o/] # => "o"
|
|
* 'foo'[/x/] # => nil
|
|
* s = 'hello there'
|
|
* s[/[aeiou](.)\1/] # => "ell"
|
|
* s[/[aeiou](.)\1/, 0] # => "ell"
|
|
*
|
|
* If argument +capture+ is given and not <tt>0</tt>,
|
|
* it should be either an \Integer capture group index or a \String or \Symbol capture group name;
|
|
* the method call returns only the specified capture
|
|
* (see {Regexp Capturing}[Regexp.html#class-Regexp-label-Capturing]):
|
|
* s = 'hello there'
|
|
* s[/[aeiou](.)\1/, 1] # => "l"
|
|
* s[/(?<vowel>[aeiou])(?<non_vowel>[^aeiou])/, "non_vowel"] # => "l"
|
|
* s[/(?<vowel>[aeiou])(?<non_vowel>[^aeiou])/, :vowel] # => "e"
|
|
*
|
|
* If an invalid capture group index is given, +nil+ is returned. If an invalid
|
|
* capture group name is given, +IndexError+ is raised.
|
|
*
|
|
* When the single \String argument +substring+ is given,
|
|
* returns the substring from +self+ if found, otherwise +nil+:
|
|
* 'foo'['oo'] # => "oo"
|
|
* 'foo'['xx'] # => nil
|
|
*
|
|
* String#slice is an alias for String#[].
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_aref_m(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
if (argc == 2) {
|
|
if (RB_TYPE_P(argv[0], T_REGEXP)) {
|
|
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);
|
|
}
|
|
}
|
|
rb_check_arity(argc, 1, 2);
|
|
return rb_str_aref(str, argv[0]);
|
|
}
|
|
|
|
VALUE
|
|
rb_str_drop_bytes(VALUE str, long len)
|
|
{
|
|
char *ptr = RSTRING_PTR(str);
|
|
long olen = RSTRING_LEN(str), nlen;
|
|
|
|
str_modifiable(str);
|
|
if (len > olen) len = olen;
|
|
nlen = olen - len;
|
|
if (STR_EMBEDDABLE_P(nlen, TERM_LEN(str))) {
|
|
char *oldptr = ptr;
|
|
int fl = (int)(RBASIC(str)->flags & (STR_NOEMBED|STR_SHARED|STR_NOFREE));
|
|
STR_SET_EMBED(str);
|
|
STR_SET_EMBED_LEN(str, nlen);
|
|
ptr = RSTRING(str)->as.ary;
|
|
memmove(ptr, oldptr + len, nlen);
|
|
if (fl == STR_NOEMBED) xfree(oldptr);
|
|
}
|
|
else {
|
|
if (!STR_SHARED_P(str)) rb_str_new_frozen(str);
|
|
ptr = RSTRING(str)->as.heap.ptr += len;
|
|
RSTRING(str)->as.heap.len = nlen;
|
|
}
|
|
ptr[nlen] = 0;
|
|
ENC_CODERANGE_CLEAR(str);
|
|
return str;
|
|
}
|
|
|
|
static void
|
|
rb_str_splice_0(VALUE str, long beg, long len, VALUE val)
|
|
{
|
|
char *sptr;
|
|
long slen, vlen = RSTRING_LEN(val);
|
|
int cr;
|
|
|
|
if (beg == 0 && vlen == 0) {
|
|
rb_str_drop_bytes(str, len);
|
|
return;
|
|
}
|
|
|
|
str_modify_keep_cr(str);
|
|
RSTRING_GETMEM(str, sptr, slen);
|
|
if (len < vlen) {
|
|
/* expand string */
|
|
RESIZE_CAPA(str, slen + vlen - len);
|
|
sptr = RSTRING_PTR(str);
|
|
}
|
|
|
|
if (ENC_CODERANGE(str) == ENC_CODERANGE_7BIT)
|
|
cr = rb_enc_str_coderange(val);
|
|
else
|
|
cr = ENC_CODERANGE_UNKNOWN;
|
|
|
|
if (vlen != len) {
|
|
memmove(sptr + beg + vlen,
|
|
sptr + beg + len,
|
|
slen - (beg + len));
|
|
}
|
|
if (vlen < beg && len < 0) {
|
|
MEMZERO(sptr + slen, char, -len);
|
|
}
|
|
if (vlen > 0) {
|
|
memmove(sptr + beg, RSTRING_PTR(val), vlen);
|
|
}
|
|
slen += vlen - len;
|
|
STR_SET_LEN(str, slen);
|
|
TERM_FILL(&sptr[slen], TERM_LEN(str));
|
|
ENC_CODERANGE_SET(str, cr);
|
|
}
|
|
|
|
void
|
|
rb_str_update(VALUE str, long beg, long len, VALUE val)
|
|
{
|
|
long slen;
|
|
char *p, *e;
|
|
rb_encoding *enc;
|
|
int singlebyte = single_byte_optimizable(str);
|
|
int cr;
|
|
|
|
if (len < 0) rb_raise(rb_eIndexError, "negative length %ld", len);
|
|
|
|
StringValue(val);
|
|
enc = rb_enc_check(str, val);
|
|
slen = str_strlen(str, enc); /* rb_enc_check */
|
|
|
|
if ((slen < beg) || ((beg < 0) && (beg + slen < 0))) {
|
|
rb_raise(rb_eIndexError, "index %ld out of string", beg);
|
|
}
|
|
if (beg < 0) {
|
|
beg += slen;
|
|
}
|
|
assert(beg >= 0);
|
|
assert(beg <= slen);
|
|
if (len > slen - beg) {
|
|
len = slen - beg;
|
|
}
|
|
str_modify_keep_cr(str);
|
|
p = str_nth(RSTRING_PTR(str), RSTRING_END(str), beg, enc, singlebyte);
|
|
if (!p) p = RSTRING_END(str);
|
|
e = str_nth(p, RSTRING_END(str), len, enc, singlebyte);
|
|
if (!e) e = RSTRING_END(str);
|
|
/* error check */
|
|
beg = p - RSTRING_PTR(str); /* physical position */
|
|
len = e - p; /* physical length */
|
|
rb_str_splice_0(str, beg, len, val);
|
|
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);
|
|
}
|
|
|
|
#define rb_str_splice(str, beg, len, val) rb_str_update(str, beg, len, val)
|
|
|
|
static void
|
|
rb_str_subpat_set(VALUE str, VALUE re, VALUE backref, VALUE val)
|
|
{
|
|
int nth;
|
|
VALUE match;
|
|
long start, end, len;
|
|
rb_encoding *enc;
|
|
struct re_registers *regs;
|
|
|
|
if (rb_reg_search(re, str, 0, 0) < 0) {
|
|
rb_raise(rb_eIndexError, "regexp not matched");
|
|
}
|
|
match = rb_backref_get();
|
|
nth = rb_reg_backref_number(match, backref);
|
|
regs = RMATCH_REGS(match);
|
|
if ((nth >= regs->num_regs) || ((nth < 0) && (-nth >= regs->num_regs))) {
|
|
rb_raise(rb_eIndexError, "index %d out of regexp", nth);
|
|
}
|
|
if (nth < 0) {
|
|
nth += regs->num_regs;
|
|
}
|
|
|
|
start = BEG(nth);
|
|
if (start == -1) {
|
|
rb_raise(rb_eIndexError, "regexp group %d not matched", nth);
|
|
}
|
|
end = END(nth);
|
|
len = end - start;
|
|
StringValue(val);
|
|
enc = rb_enc_check_str(str, val);
|
|
rb_str_splice_0(str, start, len, val);
|
|
rb_enc_associate(str, enc);
|
|
}
|
|
|
|
static VALUE
|
|
rb_str_aset(VALUE str, VALUE indx, VALUE val)
|
|
{
|
|
long idx, beg;
|
|
|
|
switch (TYPE(indx)) {
|
|
case T_REGEXP:
|
|
rb_str_subpat_set(str, indx, INT2FIX(0), val);
|
|
return val;
|
|
|
|
case T_STRING:
|
|
beg = rb_str_index(str, indx, 0);
|
|
if (beg < 0) {
|
|
rb_raise(rb_eIndexError, "string not matched");
|
|
}
|
|
beg = rb_str_sublen(str, beg);
|
|
rb_str_splice(str, beg, str_strlen(indx, NULL), val);
|
|
return val;
|
|
|
|
default:
|
|
/* check if indx is Range */
|
|
{
|
|
long beg, len;
|
|
if (rb_range_beg_len(indx, &beg, &len, str_strlen(str, NULL), 2)) {
|
|
rb_str_splice(str, beg, len, val);
|
|
return val;
|
|
}
|
|
}
|
|
/* FALLTHROUGH */
|
|
|
|
case T_FIXNUM:
|
|
idx = NUM2LONG(indx);
|
|
rb_str_splice(str, idx, 1, val);
|
|
return val;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str[integer] = new_str
|
|
* str[integer, integer] = new_str
|
|
* str[range] = aString
|
|
* str[regexp] = new_str
|
|
* str[regexp, integer] = new_str
|
|
* str[regexp, name] = new_str
|
|
* str[other_str] = new_str
|
|
*
|
|
* Element Assignment---Replaces some or all of the content of
|
|
* <i>str</i>. The portion of the string affected is determined using
|
|
* the same criteria as String#[]. If the replacement string is not
|
|
* the same length as the text it is replacing, the string will be
|
|
* adjusted accordingly. If the regular expression or string is used
|
|
* as the index doesn't match a position in the string, IndexError is
|
|
* raised. If the regular expression form is used, the optional
|
|
* second Integer allows you to specify which portion of the match to
|
|
* replace (effectively using the MatchData indexing rules. The forms
|
|
* that take an Integer will raise an IndexError if the value is out
|
|
* of range; the Range form will raise a RangeError, and the Regexp
|
|
* and String will raise an IndexError on negative match.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_aset_m(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
if (argc == 3) {
|
|
if (RB_TYPE_P(argv[0], T_REGEXP)) {
|
|
rb_str_subpat_set(str, argv[0], argv[1], argv[2]);
|
|
}
|
|
else {
|
|
rb_str_splice(str, NUM2LONG(argv[0]), NUM2LONG(argv[1]), argv[2]);
|
|
}
|
|
return argv[2];
|
|
}
|
|
rb_check_arity(argc, 2, 3);
|
|
return rb_str_aset(str, argv[0], argv[1]);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* string.insert(index, other_string) -> self
|
|
*
|
|
* Inserts the given +other_string+ into +self+; returns +self+.
|
|
*
|
|
* If the \Integer +index+ is positive, inserts +other_string+ at offset +index+:
|
|
* 'foo'.insert(1, 'bar') # => "fbaroo"
|
|
*
|
|
* If the \Integer +index+ is negative, counts backward from the end of +self+
|
|
* and inserts +other_string+ at offset <tt>index+1</tt>
|
|
* (that is, _after_ <tt>self[index]</tt>):
|
|
* 'foo'.insert(-2, 'bar') # => "fobaro"
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_insert(VALUE str, VALUE idx, VALUE str2)
|
|
{
|
|
long pos = NUM2LONG(idx);
|
|
|
|
if (pos == -1) {
|
|
return rb_str_append(str, str2);
|
|
}
|
|
else if (pos < 0) {
|
|
pos++;
|
|
}
|
|
rb_str_splice(str, pos, 0, str2);
|
|
return str;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.slice!(integer) -> new_str or nil
|
|
* str.slice!(integer, integer) -> new_str or nil
|
|
* str.slice!(range) -> new_str or nil
|
|
* str.slice!(regexp) -> new_str or nil
|
|
* str.slice!(other_str) -> new_str or nil
|
|
*
|
|
* Deletes the specified portion from <i>str</i>, and returns the portion
|
|
* deleted.
|
|
*
|
|
* 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"
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_slice_bang(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
VALUE result = Qnil;
|
|
VALUE indx;
|
|
long beg, len = 1;
|
|
char *p;
|
|
|
|
rb_check_arity(argc, 1, 2);
|
|
str_modify_keep_cr(str);
|
|
indx = argv[0];
|
|
if (RB_TYPE_P(indx, T_REGEXP)) {
|
|
if (rb_reg_search(indx, str, 0, 0) < 0) return Qnil;
|
|
VALUE match = rb_backref_get();
|
|
struct re_registers *regs = RMATCH_REGS(match);
|
|
int nth = 0;
|
|
if (argc > 1 && (nth = rb_reg_backref_number(match, argv[1])) < 0) {
|
|
if ((nth += regs->num_regs) <= 0) return Qnil;
|
|
}
|
|
else if (nth >= regs->num_regs) return Qnil;
|
|
beg = BEG(nth);
|
|
len = END(nth) - beg;
|
|
goto subseq;
|
|
}
|
|
else if (argc == 2) {
|
|
beg = NUM2LONG(indx);
|
|
len = NUM2LONG(argv[1]);
|
|
goto num_index;
|
|
}
|
|
else if (FIXNUM_P(indx)) {
|
|
beg = FIX2LONG(indx);
|
|
if (!(p = rb_str_subpos(str, beg, &len))) return Qnil;
|
|
if (!len) return Qnil;
|
|
beg = p - RSTRING_PTR(str);
|
|
goto subseq;
|
|
}
|
|
else if (RB_TYPE_P(indx, T_STRING)) {
|
|
beg = rb_str_index(str, indx, 0);
|
|
if (beg == -1) return Qnil;
|
|
len = RSTRING_LEN(indx);
|
|
result = str_duplicate(rb_cString, indx);
|
|
goto squash;
|
|
}
|
|
else {
|
|
switch (rb_range_beg_len(indx, &beg, &len, str_strlen(str, NULL), 0)) {
|
|
case Qnil:
|
|
return Qnil;
|
|
case Qfalse:
|
|
beg = NUM2LONG(indx);
|
|
if (!(p = rb_str_subpos(str, beg, &len))) return Qnil;
|
|
if (!len) return Qnil;
|
|
beg = p - RSTRING_PTR(str);
|
|
goto subseq;
|
|
default:
|
|
goto num_index;
|
|
}
|
|
}
|
|
|
|
num_index:
|
|
if (!(p = rb_str_subpos(str, beg, &len))) return Qnil;
|
|
beg = p - RSTRING_PTR(str);
|
|
|
|
subseq:
|
|
result = rb_str_new(RSTRING_PTR(str)+beg, len);
|
|
rb_enc_cr_str_copy_for_substr(result, str);
|
|
|
|
squash:
|
|
if (len > 0) {
|
|
if (beg == 0) {
|
|
rb_str_drop_bytes(str, len);
|
|
}
|
|
else {
|
|
char *sptr = RSTRING_PTR(str);
|
|
long slen = RSTRING_LEN(str);
|
|
if (beg + len > slen) /* pathological check */
|
|
len = slen - beg;
|
|
memmove(sptr + beg,
|
|
sptr + beg + len,
|
|
slen - (beg + len));
|
|
slen -= len;
|
|
STR_SET_LEN(str, slen);
|
|
TERM_FILL(&sptr[slen], TERM_LEN(str));
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static VALUE
|
|
get_pat(VALUE pat)
|
|
{
|
|
VALUE val;
|
|
|
|
switch (OBJ_BUILTIN_TYPE(pat)) {
|
|
case T_REGEXP:
|
|
return pat;
|
|
|
|
case T_STRING:
|
|
break;
|
|
|
|
default:
|
|
val = rb_check_string_type(pat);
|
|
if (NIL_P(val)) {
|
|
Check_Type(pat, T_REGEXP);
|
|
}
|
|
pat = val;
|
|
}
|
|
|
|
return rb_reg_regcomp(pat);
|
|
}
|
|
|
|
static VALUE
|
|
get_pat_quoted(VALUE pat, int check)
|
|
{
|
|
VALUE val;
|
|
|
|
switch (OBJ_BUILTIN_TYPE(pat)) {
|
|
case T_REGEXP:
|
|
return pat;
|
|
|
|
case T_STRING:
|
|
break;
|
|
|
|
default:
|
|
val = rb_check_string_type(pat);
|
|
if (NIL_P(val)) {
|
|
Check_Type(pat, T_REGEXP);
|
|
}
|
|
pat = val;
|
|
}
|
|
if (check && is_broken_string(pat)) {
|
|
rb_exc_raise(rb_reg_check_preprocess(pat));
|
|
}
|
|
return pat;
|
|
}
|
|
|
|
static long
|
|
rb_pat_search(VALUE pat, VALUE str, long pos, int set_backref_str)
|
|
{
|
|
if (BUILTIN_TYPE(pat) == T_STRING) {
|
|
pos = rb_strseq_index(str, pat, pos, 1);
|
|
if (set_backref_str) {
|
|
if (pos >= 0) {
|
|
str = rb_str_new_frozen_String(str);
|
|
rb_backref_set_string(str, pos, RSTRING_LEN(pat));
|
|
}
|
|
else {
|
|
rb_backref_set(Qnil);
|
|
}
|
|
}
|
|
return pos;
|
|
}
|
|
else {
|
|
return rb_reg_search0(pat, str, pos, 0, set_backref_str);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.sub!(pattern, replacement) -> str or nil
|
|
* str.sub!(pattern) {|match| block } -> str or nil
|
|
*
|
|
* Performs the same substitution as String#sub in-place.
|
|
*
|
|
* Returns +str+ if a substitution was performed or +nil+ if no substitution
|
|
* was performed.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_sub_bang(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
VALUE pat, repl, hash = Qnil;
|
|
int iter = 0;
|
|
long plen;
|
|
int min_arity = rb_block_given_p() ? 1 : 2;
|
|
long beg;
|
|
|
|
rb_check_arity(argc, min_arity, 2);
|
|
if (argc == 1) {
|
|
iter = 1;
|
|
}
|
|
else {
|
|
repl = argv[1];
|
|
hash = rb_check_hash_type(argv[1]);
|
|
if (NIL_P(hash)) {
|
|
StringValue(repl);
|
|
}
|
|
}
|
|
|
|
pat = get_pat_quoted(argv[0], 1);
|
|
|
|
str_modifiable(str);
|
|
beg = rb_pat_search(pat, str, 0, 1);
|
|
if (beg >= 0) {
|
|
rb_encoding *enc;
|
|
int cr = ENC_CODERANGE(str);
|
|
long beg0, end0;
|
|
VALUE match, match0 = Qnil;
|
|
struct re_registers *regs;
|
|
char *p, *rp;
|
|
long len, rlen;
|
|
|
|
match = rb_backref_get();
|
|
regs = RMATCH_REGS(match);
|
|
if (RB_TYPE_P(pat, T_STRING)) {
|
|
beg0 = beg;
|
|
end0 = beg0 + RSTRING_LEN(pat);
|
|
match0 = pat;
|
|
}
|
|
else {
|
|
beg0 = BEG(0);
|
|
end0 = END(0);
|
|
if (iter) match0 = rb_reg_nth_match(0, match);
|
|
}
|
|
|
|
if (iter || !NIL_P(hash)) {
|
|
p = RSTRING_PTR(str); len = RSTRING_LEN(str);
|
|
|
|
if (iter) {
|
|
repl = rb_obj_as_string(rb_yield(match0));
|
|
}
|
|
else {
|
|
repl = rb_hash_aref(hash, rb_str_subseq(str, beg0, end0 - beg0));
|
|
repl = rb_obj_as_string(repl);
|
|
}
|
|
str_mod_check(str, p, len);
|
|
rb_check_frozen(str);
|
|
}
|
|
else {
|
|
repl = rb_reg_regsub(repl, str, regs, RB_TYPE_P(pat, T_STRING) ? Qnil : pat);
|
|
}
|
|
|
|
enc = rb_enc_compatible(str, repl);
|
|
if (!enc) {
|
|
rb_encoding *str_enc = STR_ENC_GET(str);
|
|
p = RSTRING_PTR(str); len = RSTRING_LEN(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)));
|
|
}
|
|
enc = STR_ENC_GET(repl);
|
|
}
|
|
rb_str_modify(str);
|
|
rb_enc_associate(str, enc);
|
|
if (ENC_CODERANGE_UNKNOWN < cr && cr < ENC_CODERANGE_BROKEN) {
|
|
int cr2 = ENC_CODERANGE(repl);
|
|
if (cr2 == ENC_CODERANGE_BROKEN ||
|
|
(cr == ENC_CODERANGE_VALID && cr2 == ENC_CODERANGE_7BIT))
|
|
cr = ENC_CODERANGE_UNKNOWN;
|
|
else
|
|
cr = cr2;
|
|
}
|
|
plen = end0 - beg0;
|
|
rlen = RSTRING_LEN(repl);
|
|
len = RSTRING_LEN(str);
|
|
if (rlen > plen) {
|
|
RESIZE_CAPA(str, len + rlen - plen);
|
|
}
|
|
p = RSTRING_PTR(str);
|
|
if (rlen != plen) {
|
|
memmove(p + beg0 + rlen, p + beg0 + plen, len - beg0 - plen);
|
|
}
|
|
rp = RSTRING_PTR(repl);
|
|
memmove(p + beg0, rp, rlen);
|
|
len += rlen - plen;
|
|
STR_SET_LEN(str, len);
|
|
TERM_FILL(&RSTRING_PTR(str)[len], TERM_LEN(str));
|
|
ENC_CODERANGE_SET(str, cr);
|
|
|
|
return str;
|
|
}
|
|
return Qnil;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.sub(pattern, replacement) -> new_str
|
|
* str.sub(pattern, hash) -> new_str
|
|
* str.sub(pattern) {|match| block } -> new_str
|
|
*
|
|
* Returns a copy of +str+ with the _first_ occurrence of +pattern+
|
|
* replaced by the second argument. The +pattern+ is typically a Regexp; if
|
|
* given as a String, any regular expression metacharacters it contains will
|
|
* be interpreted literally, e.g. <code>\d</code> will match a backslash
|
|
* followed by 'd', instead of a digit.
|
|
*
|
|
* If +replacement+ is a String it will be substituted for the matched text.
|
|
* It may contain back-references to the pattern's capture groups of the form
|
|
* <code>\d</code>, where <i>d</i> is a group number, or
|
|
* <code>\k<n></code>, where <i>n</i> is a group name.
|
|
* Similarly, <code>\&</code>, <code>\'</code>, <code>\`</code>, and
|
|
* <code>\+</code> correspond to special variables, <code>$&</code>,
|
|
* <code>$'</code>, <code>$`</code>, and <code>$+</code>, respectively.
|
|
* (See rdoc-ref:regexp.rdoc for details.)
|
|
* <code>\0</code> is the same as <code>\&</code>.
|
|
* <code>\\\\</code> is interpreted as an escape, i.e., a single backslash.
|
|
* Note that, within +replacement+ the special match variables, such as
|
|
* <code>$&</code>, will not refer to the current match.
|
|
*
|
|
* If the second argument is a Hash, and the matched text is one of its keys,
|
|
* the corresponding value is the replacement string.
|
|
*
|
|
* In the block form, the current match string is passed in as a parameter,
|
|
* and variables such as <code>$1</code>, <code>$2</code>, <code>$`</code>,
|
|
* <code>$&</code>, and <code>$'</code> will be set appropriately.
|
|
* (See rdoc-ref:regexp.rdoc for details.)
|
|
* The value returned by the block will be substituted for the match on each
|
|
* call.
|
|
*
|
|
* "hello".sub(/[aeiou]/, '*') #=> "h*llo"
|
|
* "hello".sub(/([aeiou])/, '<\1>') #=> "h<e>llo"
|
|
* "hello".sub(/./) {|s| s.ord.to_s + ' ' } #=> "104 ello"
|
|
* "hello".sub(/(?<foo>[aeiou])/, '*\k<foo>*') #=> "h*e*llo"
|
|
* 'Is SHELL your preferred shell?'.sub(/[[:upper:]]{2,}/, ENV)
|
|
* #=> "Is /bin/bash your preferred shell?"
|
|
*
|
|
* Note that a string literal consumes backslashes.
|
|
* (See rdoc-ref:syntax/literals.rdoc for details about string literals.)
|
|
* Back-references are typically preceded by an additional backslash.
|
|
* For example, if you want to write a back-reference <code>\&</code> in
|
|
* +replacement+ with a double-quoted string literal, you need to write:
|
|
* <code>"..\\\\&.."</code>.
|
|
* If you want to write a non-back-reference string <code>\&</code> in
|
|
* +replacement+, you need first to escape the backslash to prevent
|
|
* this method from interpreting it as a back-reference, and then you
|
|
* need to escape the backslashes again to prevent a string literal from
|
|
* consuming them: <code>"..\\\\\\\\&.."</code>.
|
|
* You may want to use the block form to avoid a lot of backslashes.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_sub(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
str = str_duplicate(rb_cString, str);
|
|
rb_str_sub_bang(argc, argv, str);
|
|
return 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;
|
|
long beg, beg0, end0;
|
|
long offset, blen, slen, len, last;
|
|
enum {STR, ITER, MAP} mode = STR;
|
|
char *sp, *cp;
|
|
int need_backref = -1;
|
|
rb_encoding *str_enc;
|
|
|
|
switch (argc) {
|
|
case 1:
|
|
RETURN_ENUMERATOR(str, argc, argv);
|
|
mode = ITER;
|
|
break;
|
|
case 2:
|
|
repl = argv[1];
|
|
hash = rb_check_hash_type(argv[1]);
|
|
if (NIL_P(hash)) {
|
|
StringValue(repl);
|
|
}
|
|
else {
|
|
mode = MAP;
|
|
}
|
|
break;
|
|
default:
|
|
rb_error_arity(argc, 1, 2);
|
|
}
|
|
|
|
pat = get_pat_quoted(argv[0], 1);
|
|
beg = rb_pat_search(pat, str, 0, need_backref);
|
|
if (beg < 0) {
|
|
if (bang) return Qnil; /* no match, no substitution */
|
|
return str_duplicate(rb_cString, str);
|
|
}
|
|
|
|
offset = 0;
|
|
blen = RSTRING_LEN(str) + 30; /* len + margin */
|
|
dest = rb_str_buf_new(blen);
|
|
sp = RSTRING_PTR(str);
|
|
slen = RSTRING_LEN(str);
|
|
cp = sp;
|
|
str_enc = STR_ENC_GET(str);
|
|
rb_enc_associate(dest, str_enc);
|
|
ENC_CODERANGE_SET(dest, rb_enc_asciicompat(str_enc) ? ENC_CODERANGE_7BIT : ENC_CODERANGE_VALID);
|
|
|
|
do {
|
|
match = rb_backref_get();
|
|
regs = RMATCH_REGS(match);
|
|
if (RB_TYPE_P(pat, T_STRING)) {
|
|
beg0 = beg;
|
|
end0 = beg0 + RSTRING_LEN(pat);
|
|
match0 = pat;
|
|
}
|
|
else {
|
|
beg0 = BEG(0);
|
|
end0 = END(0);
|
|
if (mode == ITER) match0 = rb_reg_nth_match(0, match);
|
|
}
|
|
|
|
if (mode) {
|
|
if (mode == ITER) {
|
|
val = rb_obj_as_string(rb_yield(match0));
|
|
}
|
|
else {
|
|
val = rb_hash_aref(hash, rb_str_subseq(str, beg0, end0 - beg0));
|
|
val = rb_obj_as_string(val);
|
|
}
|
|
str_mod_check(str, sp, slen);
|
|
if (val == dest) { /* paranoid check [ruby-dev:24827] */
|
|
rb_raise(rb_eRuntimeError, "block should not cheat");
|
|
}
|
|
}
|
|
else if (need_backref) {
|
|
val = rb_reg_regsub(repl, str, regs, RB_TYPE_P(pat, T_STRING) ? Qnil : pat);
|
|
if (need_backref < 0) {
|
|
need_backref = val != repl;
|
|
}
|
|
}
|
|
else {
|
|
val = repl;
|
|
}
|
|
|
|
len = beg0 - offset; /* copy pre-match substr */
|
|
if (len) {
|
|
rb_enc_str_buf_cat(dest, cp, len, str_enc);
|
|
}
|
|
|
|
rb_str_buf_append(dest, val);
|
|
|
|
last = offset;
|
|
offset = end0;
|
|
if (beg0 == end0) {
|
|
/*
|
|
* Always consume at least one character of the input string
|
|
* in order to prevent infinite loops.
|
|
*/
|
|
if (RSTRING_LEN(str) <= end0) break;
|
|
len = rb_enc_fast_mbclen(RSTRING_PTR(str)+end0, RSTRING_END(str), str_enc);
|
|
rb_enc_str_buf_cat(dest, RSTRING_PTR(str)+end0, len, str_enc);
|
|
offset = end0 + len;
|
|
}
|
|
cp = RSTRING_PTR(str) + offset;
|
|
if (offset > RSTRING_LEN(str)) break;
|
|
beg = rb_pat_search(pat, str, offset, need_backref);
|
|
} 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);
|
|
if (bang) {
|
|
str_shared_replace(str, dest);
|
|
}
|
|
else {
|
|
str = dest;
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.gsub!(pattern, replacement) -> str or nil
|
|
* str.gsub!(pattern, hash) -> str or nil
|
|
* str.gsub!(pattern) {|match| block } -> str or nil
|
|
* str.gsub!(pattern) -> an_enumerator
|
|
*
|
|
* Performs the substitutions of String#gsub in place, returning
|
|
* <i>str</i>, or <code>nil</code> if no substitutions were
|
|
* performed. If no block and no <i>replacement</i> is given, an
|
|
* enumerator is returned instead.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_gsub_bang(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
str_modify_keep_cr(str);
|
|
return str_gsub(argc, argv, str, 1);
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.gsub(pattern, replacement) -> new_str
|
|
* str.gsub(pattern, hash) -> new_str
|
|
* str.gsub(pattern) {|match| block } -> new_str
|
|
* str.gsub(pattern) -> enumerator
|
|
*
|
|
* Returns a copy of <i>str</i> with <em>all</em> occurrences of
|
|
* <i>pattern</i> substituted for the second argument. The <i>pattern</i> is
|
|
* typically a Regexp; if given as a String, any
|
|
* regular expression metacharacters it contains will be interpreted
|
|
* literally, e.g. <code>\d</code> will match a backslash followed by 'd',
|
|
* instead of a digit.
|
|
*
|
|
* If +replacement+ is a String it will be substituted for the matched text.
|
|
* It may contain back-references to the pattern's capture groups of the form
|
|
* <code>\d</code>, where <i>d</i> is a group number, or
|
|
* <code>\k<n></code>, where <i>n</i> is a group name.
|
|
* Similarly, <code>\&</code>, <code>\'</code>, <code>\`</code>, and
|
|
* <code>\+</code> correspond to special variables, <code>$&</code>,
|
|
* <code>$'</code>, <code>$`</code>, and <code>$+</code>, respectively.
|
|
* (See rdoc-ref:regexp.rdoc for details.)
|
|
* <code>\0</code> is the same as <code>\&</code>.
|
|
* <code>\\\\</code> is interpreted as an escape, i.e., a single backslash.
|
|
* Note that, within +replacement+ the special match variables, such as
|
|
* <code>$&</code>, will not refer to the current match.
|
|
*
|
|
* If the second argument is a Hash, and the matched text is one
|
|
* of its keys, the corresponding value is the replacement string.
|
|
*
|
|
* In the block form, the current match string is passed in as a parameter,
|
|
* and variables such as <code>$1</code>, <code>$2</code>, <code>$`</code>,
|
|
* <code>$&</code>, and <code>$'</code> will be set appropriately.
|
|
* (See rdoc-ref:regexp.rdoc for details.)
|
|
* The value returned by the block will be substituted for the match on each
|
|
* call.
|
|
*
|
|
* When neither a block nor a second argument is supplied, an
|
|
* Enumerator is returned.
|
|
*
|
|
* "hello".gsub(/[aeiou]/, '*') #=> "h*ll*"
|
|
* "hello".gsub(/([aeiou])/, '<\1>') #=> "h<e>ll<o>"
|
|
* "hello".gsub(/./) {|s| s.ord.to_s + ' '} #=> "104 101 108 108 111 "
|
|
* "hello".gsub(/(?<foo>[aeiou])/, '{\k<foo>}') #=> "h{e}ll{o}"
|
|
* 'hello'.gsub(/[eo]/, 'e' => 3, 'o' => '*') #=> "h3ll*"
|
|
*
|
|
* Note that a string literal consumes backslashes.
|
|
* (See rdoc-ref:syntax/literals.rdoc for details on string literals.)
|
|
* Back-references are typically preceded by an additional backslash.
|
|
* For example, if you want to write a back-reference <code>\&</code> in
|
|
* +replacement+ with a double-quoted string literal, you need to write:
|
|
* <code>"..\\\\&.."</code>.
|
|
* If you want to write a non-back-reference string <code>\&</code> in
|
|
* +replacement+, you need first to escape the backslash to prevent
|
|
* this method from interpreting it as a back-reference, and then you
|
|
* need to escape the backslashes again to prevent a string literal from
|
|
* consuming them: <code>"..\\\\\\\\&.."</code>.
|
|
* You may want to use the block form to avoid a lot of backslashes.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_gsub(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
return str_gsub(argc, argv, str, 0);
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.replace(other_str) -> str
|
|
*
|
|
* Replaces the contents of <i>str</i> with the corresponding
|
|
* values in <i>other_str</i>.
|
|
*
|
|
* s = "hello" #=> "hello"
|
|
* s.replace "world" #=> "world"
|
|
*/
|
|
|
|
VALUE
|
|
rb_str_replace(VALUE str, VALUE str2)
|
|
{
|
|
str_modifiable(str);
|
|
if (str == str2) return str;
|
|
|
|
StringValue(str2);
|
|
str_discard(str);
|
|
return str_replace(str, str2);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* string.clear -> string
|
|
*
|
|
* Makes string empty.
|
|
*
|
|
* a = "abcde"
|
|
* a.clear #=> ""
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_clear(VALUE str)
|
|
{
|
|
str_discard(str);
|
|
STR_SET_EMBED(str);
|
|
STR_SET_EMBED_LEN(str, 0);
|
|
RSTRING_PTR(str)[0] = 0;
|
|
if (rb_enc_asciicompat(STR_ENC_GET(str)))
|
|
ENC_CODERANGE_SET(str, ENC_CODERANGE_7BIT);
|
|
else
|
|
ENC_CODERANGE_SET(str, ENC_CODERANGE_VALID);
|
|
return str;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* string.chr -> string
|
|
*
|
|
* Returns a one-character string at the beginning of the string.
|
|
*
|
|
* a = "abcde"
|
|
* a.chr #=> "a"
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_chr(VALUE str)
|
|
{
|
|
return rb_str_substr(str, 0, 1);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.getbyte(index) -> 0 .. 255
|
|
*
|
|
* returns the <i>index</i>th byte as an integer.
|
|
*/
|
|
static VALUE
|
|
rb_str_getbyte(VALUE str, VALUE index)
|
|
{
|
|
long pos = NUM2LONG(index);
|
|
|
|
if (pos < 0)
|
|
pos += RSTRING_LEN(str);
|
|
if (pos < 0 || RSTRING_LEN(str) <= pos)
|
|
return Qnil;
|
|
|
|
return INT2FIX((unsigned char)RSTRING_PTR(str)[pos]);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.setbyte(index, integer) -> integer
|
|
*
|
|
* modifies the <i>index</i>th byte as <i>integer</i>.
|
|
*/
|
|
static VALUE
|
|
rb_str_setbyte(VALUE str, VALUE index, VALUE value)
|
|
{
|
|
long pos = NUM2LONG(index);
|
|
long len = RSTRING_LEN(str);
|
|
char *head, *left = 0;
|
|
unsigned char *ptr;
|
|
rb_encoding *enc;
|
|
int cr = ENC_CODERANGE_UNKNOWN, width, nlen;
|
|
|
|
if (pos < -len || len <= pos)
|
|
rb_raise(rb_eIndexError, "index %ld out of string", pos);
|
|
if (pos < 0)
|
|
pos += len;
|
|
|
|
VALUE v = rb_to_int(value);
|
|
VALUE w = rb_int_and(v, INT2FIX(0xff));
|
|
unsigned char byte = NUM2INT(w) & 0xFF;
|
|
|
|
if (!str_independent(str))
|
|
str_make_independent(str);
|
|
enc = STR_ENC_GET(str);
|
|
head = RSTRING_PTR(str);
|
|
ptr = (unsigned char *)&head[pos];
|
|
if (!STR_EMBED_P(str)) {
|
|
cr = ENC_CODERANGE(str);
|
|
switch (cr) {
|
|
case ENC_CODERANGE_7BIT:
|
|
left = (char *)ptr;
|
|
*ptr = byte;
|
|
if (ISASCII(byte)) goto end;
|
|
nlen = rb_enc_precise_mbclen(left, head+len, enc);
|
|
if (!MBCLEN_CHARFOUND_P(nlen))
|
|
ENC_CODERANGE_SET(str, ENC_CODERANGE_BROKEN);
|
|
else
|
|
ENC_CODERANGE_SET(str, ENC_CODERANGE_VALID);
|
|
goto end;
|
|
case ENC_CODERANGE_VALID:
|
|
left = rb_enc_left_char_head(head, ptr, head+len, enc);
|
|
width = rb_enc_precise_mbclen(left, head+len, enc);
|
|
*ptr = byte;
|
|
nlen = rb_enc_precise_mbclen(left, head+len, enc);
|
|
if (!MBCLEN_CHARFOUND_P(nlen))
|
|
ENC_CODERANGE_SET(str, ENC_CODERANGE_BROKEN);
|
|
else if (MBCLEN_CHARFOUND_LEN(nlen) != width || ISASCII(byte))
|
|
ENC_CODERANGE_CLEAR(str);
|
|
goto end;
|
|
}
|
|
}
|
|
ENC_CODERANGE_CLEAR(str);
|
|
*ptr = byte;
|
|
|
|
end:
|
|
return value;
|
|
}
|
|
|
|
static VALUE
|
|
str_byte_substr(VALUE str, long beg, long len, int empty)
|
|
{
|
|
char *p, *s = RSTRING_PTR(str);
|
|
long n = RSTRING_LEN(str);
|
|
VALUE str2;
|
|
|
|
if (beg > n || len < 0) return Qnil;
|
|
if (beg < 0) {
|
|
beg += n;
|
|
if (beg < 0) return Qnil;
|
|
}
|
|
if (len > n - beg)
|
|
len = n - beg;
|
|
if (len <= 0) {
|
|
if (!empty) return Qnil;
|
|
len = 0;
|
|
p = 0;
|
|
}
|
|
else
|
|
p = s + beg;
|
|
|
|
if (!STR_EMBEDDABLE_P(len, TERM_LEN(str)) && SHARABLE_SUBSTRING_P(beg, len, n)) {
|
|
str2 = rb_str_new_frozen(str);
|
|
str2 = str_new_shared(rb_cString, str2);
|
|
RSTRING(str2)->as.heap.ptr += beg;
|
|
RSTRING(str2)->as.heap.len = len;
|
|
}
|
|
else {
|
|
str2 = rb_str_new(p, len);
|
|
}
|
|
|
|
str_enc_copy(str2, str);
|
|
|
|
if (RSTRING_LEN(str2) == 0) {
|
|
if (!rb_enc_asciicompat(STR_ENC_GET(str)))
|
|
ENC_CODERANGE_SET(str2, ENC_CODERANGE_VALID);
|
|
else
|
|
ENC_CODERANGE_SET(str2, ENC_CODERANGE_7BIT);
|
|
}
|
|
else {
|
|
switch (ENC_CODERANGE(str)) {
|
|
case ENC_CODERANGE_7BIT:
|
|
ENC_CODERANGE_SET(str2, ENC_CODERANGE_7BIT);
|
|
break;
|
|
default:
|
|
ENC_CODERANGE_SET(str2, ENC_CODERANGE_UNKNOWN);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return str2;
|
|
}
|
|
|
|
static VALUE
|
|
str_byte_aref(VALUE str, VALUE indx)
|
|
{
|
|
long idx;
|
|
if (FIXNUM_P(indx)) {
|
|
idx = FIX2LONG(indx);
|
|
}
|
|
else {
|
|
/* check if indx is Range */
|
|
long beg, len = RSTRING_LEN(str);
|
|
|
|
switch (rb_range_beg_len(indx, &beg, &len, len, 0)) {
|
|
case Qfalse:
|
|
break;
|
|
case Qnil:
|
|
return Qnil;
|
|
default:
|
|
return str_byte_substr(str, beg, len, TRUE);
|
|
}
|
|
|
|
idx = NUM2LONG(indx);
|
|
}
|
|
return str_byte_substr(str, idx, 1, FALSE);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.byteslice(integer) -> new_str or nil
|
|
* str.byteslice(integer, integer) -> new_str or nil
|
|
* str.byteslice(range) -> new_str or nil
|
|
*
|
|
* Byte Reference---If passed a single Integer, returns a
|
|
* substring of one byte at that position. If passed two Integer
|
|
* objects, returns a substring starting at the offset given by the first, and
|
|
* a length given by the second. If given a Range, a substring containing
|
|
* bytes at offsets given by the range is returned. In all three cases, if
|
|
* an offset is negative, it is counted from the end of <i>str</i>. Returns
|
|
* <code>nil</code> if the initial offset falls outside the string, the length
|
|
* is negative, or the beginning of the range is greater than the end.
|
|
* The encoding of the resulted string keeps original encoding.
|
|
*
|
|
* "hello".byteslice(1) #=> "e"
|
|
* "hello".byteslice(-1) #=> "o"
|
|
* "hello".byteslice(1, 2) #=> "el"
|
|
* "\x80\u3042".byteslice(1, 3) #=> "\u3042"
|
|
* "\x03\u3042\xff".byteslice(1..3) #=> "\u3042"
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_byteslice(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
if (argc == 2) {
|
|
long beg = NUM2LONG(argv[0]);
|
|
long end = NUM2LONG(argv[1]);
|
|
return str_byte_substr(str, beg, end, TRUE);
|
|
}
|
|
rb_check_arity(argc, 1, 2);
|
|
return str_byte_aref(str, argv[0]);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.reverse -> new_str
|
|
*
|
|
* Returns a new string with the characters from <i>str</i> in reverse order.
|
|
*
|
|
* "stressed".reverse #=> "desserts"
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_reverse(VALUE str)
|
|
{
|
|
rb_encoding *enc;
|
|
VALUE rev;
|
|
char *s, *e, *p;
|
|
int cr;
|
|
|
|
if (RSTRING_LEN(str) <= 1) return str_duplicate(rb_cString, str);
|
|
enc = STR_ENC_GET(str);
|
|
rev = rb_str_new(0, RSTRING_LEN(str));
|
|
s = RSTRING_PTR(str); e = RSTRING_END(str);
|
|
p = RSTRING_END(rev);
|
|
cr = ENC_CODERANGE(str);
|
|
|
|
if (RSTRING_LEN(str) > 1) {
|
|
if (single_byte_optimizable(str)) {
|
|
while (s < e) {
|
|
*--p = *s++;
|
|
}
|
|
}
|
|
else if (cr == ENC_CODERANGE_VALID) {
|
|
while (s < e) {
|
|
int clen = rb_enc_fast_mbclen(s, e, enc);
|
|
|
|
p -= clen;
|
|
memcpy(p, s, clen);
|
|
s += clen;
|
|
}
|
|
}
|
|
else {
|
|
cr = rb_enc_asciicompat(enc) ?
|
|
ENC_CODERANGE_7BIT : ENC_CODERANGE_VALID;
|
|
while (s < e) {
|
|
int clen = rb_enc_mbclen(s, e, enc);
|
|
|
|
if (clen > 1 || (*s & 0x80)) cr = ENC_CODERANGE_UNKNOWN;
|
|
p -= clen;
|
|
memcpy(p, s, clen);
|
|
s += clen;
|
|
}
|
|
}
|
|
}
|
|
STR_SET_LEN(rev, RSTRING_LEN(str));
|
|
str_enc_copy(rev, str);
|
|
ENC_CODERANGE_SET(rev, cr);
|
|
|
|
return rev;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.reverse! -> str
|
|
*
|
|
* Reverses <i>str</i> in place.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_reverse_bang(VALUE str)
|
|
{
|
|
if (RSTRING_LEN(str) > 1) {
|
|
if (single_byte_optimizable(str)) {
|
|
char *s, *e, c;
|
|
|
|
str_modify_keep_cr(str);
|
|
s = RSTRING_PTR(str);
|
|
e = RSTRING_END(str) - 1;
|
|
while (s < e) {
|
|
c = *s;
|
|
*s++ = *e;
|
|
*e-- = c;
|
|
}
|
|
}
|
|
else {
|
|
str_shared_replace(str, rb_str_reverse(str));
|
|
}
|
|
}
|
|
else {
|
|
str_modify_keep_cr(str);
|
|
}
|
|
return str;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.include? other_str -> true or false
|
|
*
|
|
* Returns <code>true</code> if <i>str</i> contains the given string or
|
|
* character.
|
|
*
|
|
* "hello".include? "lo" #=> true
|
|
* "hello".include? "ol" #=> false
|
|
* "hello".include? ?h #=> true
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_include(VALUE str, VALUE arg)
|
|
{
|
|
long i;
|
|
|
|
StringValue(arg);
|
|
i = rb_str_index(str, arg, 0);
|
|
|
|
if (i == -1) return Qfalse;
|
|
return Qtrue;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.to_i(base=10) -> integer
|
|
*
|
|
* Returns the result of interpreting leading characters in <i>str</i> as an
|
|
* integer base <i>base</i> (between 2 and 36). Extraneous characters past the
|
|
* end of a valid number are ignored. If there is not a valid number at the
|
|
* start of <i>str</i>, <code>0</code> is returned. This method never raises an
|
|
* exception when <i>base</i> is valid.
|
|
*
|
|
* "12345".to_i #=> 12345
|
|
* "99 red balloons".to_i #=> 99
|
|
* "0a".to_i #=> 0
|
|
* "0a".to_i(16) #=> 10
|
|
* "hello".to_i #=> 0
|
|
* "1100101".to_i(2) #=> 101
|
|
* "1100101".to_i(8) #=> 294977
|
|
* "1100101".to_i(10) #=> 1100101
|
|
* "1100101".to_i(16) #=> 17826049
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_to_i(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
int base = 10;
|
|
|
|
if (rb_check_arity(argc, 0, 1) && (base = NUM2INT(argv[0])) < 0) {
|
|
rb_raise(rb_eArgError, "invalid radix %d", base);
|
|
}
|
|
return rb_str_to_inum(str, base, FALSE);
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.to_f -> float
|
|
*
|
|
* Returns the result of interpreting leading characters in <i>str</i> as a
|
|
* floating point number. Extraneous characters past the end of a valid number
|
|
* are ignored. If there is not a valid number at the start of <i>str</i>,
|
|
* <code>0.0</code> is returned. This method never raises an exception.
|
|
*
|
|
* "123.45e1".to_f #=> 1234.5
|
|
* "45.67 degrees".to_f #=> 45.67
|
|
* "thx1138".to_f #=> 0.0
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_to_f(VALUE str)
|
|
{
|
|
return DBL2NUM(rb_str_to_dbl(str, FALSE));
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.to_s -> str
|
|
* str.to_str -> str
|
|
*
|
|
* Returns +self+.
|
|
*
|
|
* If called on a subclass of String, converts the receiver to a String object.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_to_s(VALUE str)
|
|
{
|
|
if (rb_obj_class(str) != rb_cString) {
|
|
return str_duplicate(rb_cString, str);
|
|
}
|
|
return str;
|
|
}
|
|
|
|
#if 0
|
|
static void
|
|
str_cat_char(VALUE str, unsigned int c, rb_encoding *enc)
|
|
{
|
|
char s[RUBY_MAX_CHAR_LEN];
|
|
int n = rb_enc_codelen(c, enc);
|
|
|
|
rb_enc_mbcput(c, s, enc);
|
|
rb_enc_str_buf_cat(str, s, n, enc);
|
|
}
|
|
#endif
|
|
|
|
#define CHAR_ESC_LEN 13 /* sizeof(\x{ hex of 32bit unsigned int } \0) */
|
|
|
|
int
|
|
rb_str_buf_cat_escaped_char(VALUE result, unsigned int c, int unicode_p)
|
|
{
|
|
char buf[CHAR_ESC_LEN + 1];
|
|
int l;
|
|
|
|
#if SIZEOF_INT > 4
|
|
c &= 0xffffffff;
|
|
#endif
|
|
if (unicode_p) {
|
|
if (c < 0x7F && ISPRINT(c)) {
|
|
snprintf(buf, CHAR_ESC_LEN, "%c", c);
|
|
}
|
|
else if (c < 0x10000) {
|
|
snprintf(buf, CHAR_ESC_LEN, "\\u%04X", c);
|
|
}
|
|
else {
|
|
snprintf(buf, CHAR_ESC_LEN, "\\u{%X}", c);
|
|
}
|
|
}
|
|
else {
|
|
if (c < 0x100) {
|
|
snprintf(buf, CHAR_ESC_LEN, "\\x%02X", c);
|
|
}
|
|
else {
|
|
snprintf(buf, CHAR_ESC_LEN, "\\x{%X}", c);
|
|
}
|
|
}
|
|
l = (int)strlen(buf); /* CHAR_ESC_LEN cannot exceed INT_MAX */
|
|
rb_str_buf_cat(result, buf, l);
|
|
return l;
|
|
}
|
|
|
|
const char *
|
|
ruby_escaped_char(int c)
|
|
{
|
|
switch (c) {
|
|
case '\0': return "\\0";
|
|
case '\n': return "\\n";
|
|
case '\r': return "\\r";
|
|
case '\t': return "\\t";
|
|
case '\f': return "\\f";
|
|
case '\013': return "\\v";
|
|
case '\010': return "\\b";
|
|
case '\007': return "\\a";
|
|
case '\033': return "\\e";
|
|
case '\x7f': return "\\c?";
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
VALUE
|
|
rb_str_escape(VALUE str)
|
|
{
|
|
int encidx = ENCODING_GET(str);
|
|
rb_encoding *enc = rb_enc_from_index(encidx);
|
|
const char *p = RSTRING_PTR(str);
|
|
const char *pend = RSTRING_END(str);
|
|
const char *prev = p;
|
|
char buf[CHAR_ESC_LEN + 1];
|
|
VALUE result = rb_str_buf_new(0);
|
|
int unicode_p = rb_enc_unicode_p(enc);
|
|
int asciicompat = rb_enc_asciicompat(enc);
|
|
|
|
while (p < pend) {
|
|
unsigned int c;
|
|
const char *cc;
|
|
int n = rb_enc_precise_mbclen(p, pend, enc);
|
|
if (!MBCLEN_CHARFOUND_P(n)) {
|
|
if (p > prev) str_buf_cat(result, prev, p - prev);
|
|
n = rb_enc_mbminlen(enc);
|
|
if (pend < p + n)
|
|
n = (int)(pend - p);
|
|
while (n--) {
|
|
snprintf(buf, CHAR_ESC_LEN, "\\x%02X", *p & 0377);
|
|
str_buf_cat(result, buf, strlen(buf));
|
|
prev = ++p;
|
|
}
|
|
continue;
|
|
}
|
|
n = MBCLEN_CHARFOUND_LEN(n);
|
|
c = rb_enc_mbc_to_codepoint(p, pend, enc);
|
|
p += n;
|
|
cc = ruby_escaped_char(c);
|
|
if (cc) {
|
|
if (p - n > prev) str_buf_cat(result, prev, p - n - prev);
|
|
str_buf_cat(result, cc, strlen(cc));
|
|
prev = p;
|
|
}
|
|
else if (asciicompat && rb_enc_isascii(c, enc) && ISPRINT(c)) {
|
|
}
|
|
else {
|
|
if (p - n > prev) str_buf_cat(result, prev, p - n - prev);
|
|
rb_str_buf_cat_escaped_char(result, c, unicode_p);
|
|
prev = p;
|
|
}
|
|
}
|
|
if (p > prev) str_buf_cat(result, prev, p - prev);
|
|
ENCODING_CODERANGE_SET(result, rb_usascii_encindex(), ENC_CODERANGE_7BIT);
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.inspect -> string
|
|
*
|
|
* Returns a printable version of _str_, surrounded by quote marks,
|
|
* with special characters escaped.
|
|
*
|
|
* str = "hello"
|
|
* str[3] = "\b"
|
|
* str.inspect #=> "\"hel\\bo\""
|
|
*/
|
|
|
|
VALUE
|
|
rb_str_inspect(VALUE str)
|
|
{
|
|
int encidx = ENCODING_GET(str);
|
|
rb_encoding *enc = rb_enc_from_index(encidx), *actenc;
|
|
const char *p, *pend, *prev;
|
|
char buf[CHAR_ESC_LEN + 1];
|
|
VALUE result = rb_str_buf_new(0);
|
|
rb_encoding *resenc = rb_default_internal_encoding();
|
|
int unicode_p = rb_enc_unicode_p(enc);
|
|
int asciicompat = rb_enc_asciicompat(enc);
|
|
|
|
if (resenc == NULL) resenc = rb_default_external_encoding();
|
|
if (!rb_enc_asciicompat(resenc)) resenc = rb_usascii_encoding();
|
|
rb_enc_associate(result, resenc);
|
|
str_buf_cat2(result, "\"");
|
|
|
|
p = RSTRING_PTR(str); pend = RSTRING_END(str);
|
|
prev = p;
|
|
actenc = get_actual_encoding(encidx, str);
|
|
if (actenc != enc) {
|
|
enc = actenc;
|
|
if (unicode_p) unicode_p = rb_enc_unicode_p(enc);
|
|
}
|
|
while (p < pend) {
|
|
unsigned int c, cc;
|
|
int n;
|
|
|
|
n = rb_enc_precise_mbclen(p, pend, enc);
|
|
if (!MBCLEN_CHARFOUND_P(n)) {
|
|
if (p > prev) str_buf_cat(result, prev, p - prev);
|
|
n = rb_enc_mbminlen(enc);
|
|
if (pend < p + n)
|
|
n = (int)(pend - p);
|
|
while (n--) {
|
|
snprintf(buf, CHAR_ESC_LEN, "\\x%02X", *p & 0377);
|
|
str_buf_cat(result, buf, strlen(buf));
|
|
prev = ++p;
|
|
}
|
|
continue;
|
|
}
|
|
n = MBCLEN_CHARFOUND_LEN(n);
|
|
c = rb_enc_mbc_to_codepoint(p, pend, enc);
|
|
p += n;
|
|
if ((asciicompat || unicode_p) &&
|
|
(c == '"'|| c == '\\' ||
|
|
(c == '#' &&
|
|
p < pend &&
|
|
MBCLEN_CHARFOUND_P(rb_enc_precise_mbclen(p,pend,enc)) &&
|
|
(cc = rb_enc_codepoint(p,pend,enc),
|
|
(cc == '$' || cc == '@' || cc == '{'))))) {
|
|
if (p - n > prev) str_buf_cat(result, prev, p - n - prev);
|
|
str_buf_cat2(result, "\\");
|
|
if (asciicompat || enc == resenc) {
|
|
prev = p - n;
|
|
continue;
|
|
}
|
|
}
|
|
switch (c) {
|
|
case '\n': cc = 'n'; break;
|
|
case '\r': cc = 'r'; break;
|
|
case '\t': cc = 't'; break;
|
|
case '\f': cc = 'f'; break;
|
|
case '\013': cc = 'v'; break;
|
|
case '\010': cc = 'b'; break;
|
|
case '\007': cc = 'a'; break;
|
|
case 033: cc = 'e'; break;
|
|
default: cc = 0; break;
|
|
}
|
|
if (cc) {
|
|
if (p - n > prev) str_buf_cat(result, prev, p - n - prev);
|
|
buf[0] = '\\';
|
|
buf[1] = (char)cc;
|
|
str_buf_cat(result, buf, 2);
|
|
prev = p;
|
|
continue;
|
|
}
|
|
if ((enc == resenc && rb_enc_isprint(c, enc)) ||
|
|
(asciicompat && rb_enc_isascii(c, enc) && ISPRINT(c))) {
|
|
continue;
|
|
}
|
|
else {
|
|
if (p - n > prev) str_buf_cat(result, prev, p - n - prev);
|
|
rb_str_buf_cat_escaped_char(result, c, unicode_p);
|
|
prev = p;
|
|
continue;
|
|
}
|
|
}
|
|
if (p > prev) str_buf_cat(result, prev, p - prev);
|
|
str_buf_cat2(result, "\"");
|
|
|
|
return result;
|
|
}
|
|
|
|
#define IS_EVSTR(p,e) ((p) < (e) && (*(p) == '$' || *(p) == '@' || *(p) == '{'))
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.dump -> new_str
|
|
*
|
|
* Returns a quoted version of the string with all non-printing characters
|
|
* replaced by <code>\xHH</code> notation and all special characters escaped.
|
|
*
|
|
* This method can be used for round-trip: if the resulting +new_str+ is
|
|
* eval'ed, it will produce the original string.
|
|
*
|
|
* "hello \n ''".dump #=> "\"hello \\n ''\""
|
|
* "\f\x00\xff\\\"".dump #=> "\"\\f\\x00\\xFF\\\\\\\"\""
|
|
*
|
|
* See also String#undump.
|
|
*/
|
|
|
|
VALUE
|
|
rb_str_dump(VALUE str)
|
|
{
|
|
int encidx = rb_enc_get_index(str);
|
|
rb_encoding *enc = rb_enc_from_index(encidx);
|
|
long len;
|
|
const char *p, *pend;
|
|
char *q, *qend;
|
|
VALUE result;
|
|
int u8 = (encidx == rb_utf8_encindex());
|
|
static const char nonascii_suffix[] = ".dup.force_encoding(\"%s\")";
|
|
|
|
len = 2; /* "" */
|
|
if (!rb_enc_asciicompat(enc)) {
|
|
len += strlen(nonascii_suffix) - rb_strlen_lit("%s");
|
|
len += strlen(enc->name);
|
|
}
|
|
|
|
p = RSTRING_PTR(str); pend = p + RSTRING_LEN(str);
|
|
while (p < pend) {
|
|
int clen;
|
|
unsigned char c = *p++;
|
|
|
|
switch (c) {
|
|
case '"': case '\\':
|
|
case '\n': case '\r':
|
|
case '\t': case '\f':
|
|
case '\013': case '\010': case '\007': case '\033':
|
|
clen = 2;
|
|
break;
|
|
|
|
case '#':
|
|
clen = IS_EVSTR(p, pend) ? 2 : 1;
|
|
break;
|
|
|
|
default:
|
|
if (ISPRINT(c)) {
|
|
clen = 1;
|
|
}
|
|
else {
|
|
if (u8 && c > 0x7F) { /* \u notation */
|
|
int n = rb_enc_precise_mbclen(p-1, pend, enc);
|
|
if (MBCLEN_CHARFOUND_P(n)) {
|
|
unsigned int cc = rb_enc_mbc_to_codepoint(p-1, pend, enc);
|
|
if (cc <= 0xFFFF)
|
|
clen = 6; /* \uXXXX */
|
|
else if (cc <= 0xFFFFF)
|
|
clen = 9; /* \u{XXXXX} */
|
|
else
|
|
clen = 10; /* \u{XXXXXX} */
|
|
p += MBCLEN_CHARFOUND_LEN(n)-1;
|
|
break;
|
|
}
|
|
}
|
|
clen = 4; /* \xNN */
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (clen > LONG_MAX - len) {
|
|
rb_raise(rb_eRuntimeError, "string size too big");
|
|
}
|
|
len += clen;
|
|
}
|
|
|
|
result = rb_str_new(0, len);
|
|
p = RSTRING_PTR(str); pend = p + RSTRING_LEN(str);
|
|
q = RSTRING_PTR(result); qend = q + len + 1;
|
|
|
|
*q++ = '"';
|
|
while (p < pend) {
|
|
unsigned char c = *p++;
|
|
|
|
if (c == '"' || c == '\\') {
|
|
*q++ = '\\';
|
|
*q++ = c;
|
|
}
|
|
else if (c == '#') {
|
|
if (IS_EVSTR(p, pend)) *q++ = '\\';
|
|
*q++ = '#';
|
|
}
|
|
else if (c == '\n') {
|
|
*q++ = '\\';
|
|
*q++ = 'n';
|
|
}
|
|
else if (c == '\r') {
|
|
*q++ = '\\';
|
|
*q++ = 'r';
|
|
}
|
|
else if (c == '\t') {
|
|
*q++ = '\\';
|
|
*q++ = 't';
|
|
}
|
|
else if (c == '\f') {
|
|
*q++ = '\\';
|
|
*q++ = 'f';
|
|
}
|
|
else if (c == '\013') {
|
|
*q++ = '\\';
|
|
*q++ = 'v';
|
|
}
|
|
else if (c == '\010') {
|
|
*q++ = '\\';
|
|
*q++ = 'b';
|
|
}
|
|
else if (c == '\007') {
|
|
*q++ = '\\';
|
|
*q++ = 'a';
|
|
}
|
|
else if (c == '\033') {
|
|
*q++ = '\\';
|
|
*q++ = 'e';
|
|
}
|
|
else if (ISPRINT(c)) {
|
|
*q++ = c;
|
|
}
|
|
else {
|
|
*q++ = '\\';
|
|
if (u8) {
|
|
int n = rb_enc_precise_mbclen(p-1, pend, enc) - 1;
|
|
if (MBCLEN_CHARFOUND_P(n)) {
|
|
int cc = rb_enc_mbc_to_codepoint(p-1, pend, enc);
|
|
p += n;
|
|
if (cc <= 0xFFFF)
|
|
snprintf(q, qend-q, "u%04X", cc); /* \uXXXX */
|
|
else
|
|
snprintf(q, qend-q, "u{%X}", cc); /* \u{XXXXX} or \u{XXXXXX} */
|
|
q += strlen(q);
|
|
continue;
|
|
}
|
|
}
|
|
snprintf(q, qend-q, "x%02X", c);
|
|
q += 3;
|
|
}
|
|
}
|
|
*q++ = '"';
|
|
*q = '\0';
|
|
if (!rb_enc_asciicompat(enc)) {
|
|
snprintf(q, qend-q, nonascii_suffix, enc->name);
|
|
encidx = rb_ascii8bit_encindex();
|
|
}
|
|
/* result from dump is ASCII */
|
|
rb_enc_associate_index(result, encidx);
|
|
ENC_CODERANGE_SET(result, ENC_CODERANGE_7BIT);
|
|
return result;
|
|
}
|
|
|
|
static int
|
|
unescape_ascii(unsigned int c)
|
|
{
|
|
switch (c) {
|
|
case 'n':
|
|
return '\n';
|
|
case 'r':
|
|
return '\r';
|
|
case 't':
|
|
return '\t';
|
|
case 'f':
|
|
return '\f';
|
|
case 'v':
|
|
return '\13';
|
|
case 'b':
|
|
return '\010';
|
|
case 'a':
|
|
return '\007';
|
|
case 'e':
|
|
return 033;
|
|
}
|
|
UNREACHABLE_RETURN(-1);
|
|
}
|
|
|
|
static void
|
|
undump_after_backslash(VALUE undumped, const char **ss, const char *s_end, rb_encoding **penc, bool *utf8, bool *binary)
|
|
{
|
|
const char *s = *ss;
|
|
unsigned int c;
|
|
int codelen;
|
|
size_t hexlen;
|
|
unsigned char buf[6];
|
|
static rb_encoding *enc_utf8 = NULL;
|
|
|
|
switch (*s) {
|
|
case '\\':
|
|
case '"':
|
|
case '#':
|
|
rb_str_cat(undumped, s, 1); /* cat itself */
|
|
s++;
|
|
break;
|
|
case 'n':
|
|
case 'r':
|
|
case 't':
|
|
case 'f':
|
|
case 'v':
|
|
case 'b':
|
|
case 'a':
|
|
case 'e':
|
|
*buf = unescape_ascii(*s);
|
|
rb_str_cat(undumped, (char *)buf, 1);
|
|
s++;
|
|
break;
|
|
case 'u':
|
|
if (*binary) {
|
|
rb_raise(rb_eRuntimeError, "hex escape and Unicode escape are mixed");
|
|
}
|
|
*utf8 = true;
|
|
if (++s >= s_end) {
|
|
rb_raise(rb_eRuntimeError, "invalid Unicode escape");
|
|
}
|
|
if (enc_utf8 == NULL) enc_utf8 = rb_utf8_encoding();
|
|
if (*penc != enc_utf8) {
|
|
*penc = enc_utf8;
|
|
rb_enc_associate(undumped, enc_utf8);
|
|
}
|
|
if (*s == '{') { /* handle \u{...} form */
|
|
s++;
|
|
for (;;) {
|
|
if (s >= s_end) {
|
|
rb_raise(rb_eRuntimeError, "unterminated Unicode escape");
|
|
}
|
|
if (*s == '}') {
|
|
s++;
|
|
break;
|
|
}
|
|
if (ISSPACE(*s)) {
|
|
s++;
|
|
continue;
|
|
}
|
|
c = scan_hex(s, s_end-s, &hexlen);
|
|
if (hexlen == 0 || hexlen > 6) {
|
|
rb_raise(rb_eRuntimeError, "invalid Unicode escape");
|
|
}
|
|
if (c > 0x10ffff) {
|
|
rb_raise(rb_eRuntimeError, "invalid Unicode codepoint (too large)");
|
|
}
|
|
if (0xd800 <= c && c <= 0xdfff) {
|
|
rb_raise(rb_eRuntimeError, "invalid Unicode codepoint");
|
|
}
|
|
codelen = rb_enc_mbcput(c, (char *)buf, *penc);
|
|
rb_str_cat(undumped, (char *)buf, codelen);
|
|
s += hexlen;
|
|
}
|
|
}
|
|
else { /* handle \uXXXX form */
|
|
c = scan_hex(s, 4, &hexlen);
|
|
if (hexlen != 4) {
|
|
rb_raise(rb_eRuntimeError, "invalid Unicode escape");
|
|
}
|
|
if (0xd800 <= c && c <= 0xdfff) {
|
|
rb_raise(rb_eRuntimeError, "invalid Unicode codepoint");
|
|
}
|
|
codelen = rb_enc_mbcput(c, (char *)buf, *penc);
|
|
rb_str_cat(undumped, (char *)buf, codelen);
|
|
s += hexlen;
|
|
}
|
|
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");
|
|
}
|
|
*buf = scan_hex(s, 2, &hexlen);
|
|
if (hexlen != 2) {
|
|
rb_raise(rb_eRuntimeError, "invalid hex escape");
|
|
}
|
|
rb_str_cat(undumped, (char *)buf, 1);
|
|
s += hexlen;
|
|
break;
|
|
default:
|
|
rb_str_cat(undumped, s-1, 2);
|
|
s++;
|
|
}
|
|
|
|
*ss = s;
|
|
}
|
|
|
|
static VALUE rb_str_is_ascii_only_p(VALUE str);
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.undump -> new_str
|
|
*
|
|
* Returns an unescaped version of the string.
|
|
* This does the inverse of String#dump.
|
|
*
|
|
* "\"hello \\n ''\"".undump #=> "hello \n ''"
|
|
*/
|
|
|
|
static VALUE
|
|
str_undump(VALUE str)
|
|
{
|
|
const char *s = RSTRING_PTR(str);
|
|
const char *s_end = RSTRING_END(str);
|
|
rb_encoding *enc = rb_enc_get(str);
|
|
VALUE undumped = rb_enc_str_new(s, 0L, enc);
|
|
bool utf8 = false;
|
|
bool binary = false;
|
|
int w;
|
|
|
|
rb_must_asciicompat(str);
|
|
if (rb_str_is_ascii_only_p(str) == Qfalse) {
|
|
rb_raise(rb_eRuntimeError, "non-ASCII character detected");
|
|
}
|
|
if (!str_null_check(str, &w)) {
|
|
rb_raise(rb_eRuntimeError, "string contains null byte");
|
|
}
|
|
if (RSTRING_LEN(str) < 2) goto invalid_format;
|
|
if (*s != '"') goto invalid_format;
|
|
|
|
/* strip '"' at the start */
|
|
s++;
|
|
|
|
for (;;) {
|
|
if (s >= s_end) {
|
|
rb_raise(rb_eRuntimeError, "unterminated dumped string");
|
|
}
|
|
|
|
if (*s == '"') {
|
|
/* epilogue */
|
|
s++;
|
|
if (s == s_end) {
|
|
/* ascii compatible dumped string */
|
|
break;
|
|
}
|
|
else {
|
|
static const char force_encoding_suffix[] = ".force_encoding(\""; /* "\")" */
|
|
static const char dup_suffix[] = ".dup";
|
|
const char *encname;
|
|
int encidx;
|
|
ptrdiff_t size;
|
|
|
|
/* check separately for strings dumped by older versions */
|
|
size = sizeof(dup_suffix) - 1;
|
|
if (s_end - s > size && memcmp(s, dup_suffix, size) == 0) s += size;
|
|
|
|
size = sizeof(force_encoding_suffix) - 1;
|
|
if (s_end - s <= size) goto invalid_format;
|
|
if (memcmp(s, force_encoding_suffix, size) != 0) goto invalid_format;
|
|
s += size;
|
|
|
|
if (utf8) {
|
|
rb_raise(rb_eRuntimeError, "dumped string contained Unicode escape but used force_encoding");
|
|
}
|
|
|
|
encname = s;
|
|
s = memchr(s, '"', s_end-s);
|
|
size = s - encname;
|
|
if (!s) goto invalid_format;
|
|
if (s_end - s != 2) goto invalid_format;
|
|
if (s[0] != '"' || s[1] != ')') goto invalid_format;
|
|
|
|
encidx = rb_enc_find_index2(encname, (long)size);
|
|
if (encidx < 0) {
|
|
rb_raise(rb_eRuntimeError, "dumped string has unknown encoding name");
|
|
}
|
|
rb_enc_associate_index(undumped, encidx);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (*s == '\\') {
|
|
s++;
|
|
if (s >= s_end) {
|
|
rb_raise(rb_eRuntimeError, "invalid escape");
|
|
}
|
|
undump_after_backslash(undumped, &s, s_end, &enc, &utf8, &binary);
|
|
}
|
|
else {
|
|
rb_str_cat(undumped, s++, 1);
|
|
}
|
|
}
|
|
|
|
return undumped;
|
|
invalid_format:
|
|
rb_raise(rb_eRuntimeError, "invalid dumped string; not wrapped with '\"' nor '\"...\".force_encoding(\"...\")' form");
|
|
}
|
|
|
|
static void
|
|
rb_str_check_dummy_enc(rb_encoding *enc)
|
|
{
|
|
if (rb_enc_dummy_p(enc)) {
|
|
rb_raise(rb_eEncCompatError, "incompatible encoding with this operation: %s",
|
|
rb_enc_name(enc));
|
|
}
|
|
}
|
|
|
|
static rb_encoding *
|
|
str_true_enc(VALUE str)
|
|
{
|
|
rb_encoding *enc = STR_ENC_GET(str);
|
|
rb_str_check_dummy_enc(enc);
|
|
return enc;
|
|
}
|
|
|
|
static OnigCaseFoldType
|
|
check_case_options(int argc, VALUE *argv, OnigCaseFoldType flags)
|
|
{
|
|
if (argc==0)
|
|
return flags;
|
|
if (argc>2)
|
|
rb_raise(rb_eArgError, "too many options");
|
|
if (argv[0]==sym_turkic) {
|
|
flags |= ONIGENC_CASE_FOLD_TURKISH_AZERI;
|
|
if (argc==2) {
|
|
if (argv[1]==sym_lithuanian)
|
|
flags |= ONIGENC_CASE_FOLD_LITHUANIAN;
|
|
else
|
|
rb_raise(rb_eArgError, "invalid second option");
|
|
}
|
|
}
|
|
else if (argv[0]==sym_lithuanian) {
|
|
flags |= ONIGENC_CASE_FOLD_LITHUANIAN;
|
|
if (argc==2) {
|
|
if (argv[1]==sym_turkic)
|
|
flags |= ONIGENC_CASE_FOLD_TURKISH_AZERI;
|
|
else
|
|
rb_raise(rb_eArgError, "invalid second option");
|
|
}
|
|
}
|
|
else if (argc>1)
|
|
rb_raise(rb_eArgError, "too many options");
|
|
else if (argv[0]==sym_ascii)
|
|
flags |= ONIGENC_CASE_ASCII_ONLY;
|
|
else if (argv[0]==sym_fold) {
|
|
if ((flags & (ONIGENC_CASE_UPCASE|ONIGENC_CASE_DOWNCASE)) == ONIGENC_CASE_DOWNCASE)
|
|
flags ^= ONIGENC_CASE_FOLD|ONIGENC_CASE_DOWNCASE;
|
|
else
|
|
rb_raise(rb_eArgError, "option :fold only allowed for downcasing");
|
|
}
|
|
else
|
|
rb_raise(rb_eArgError, "invalid option");
|
|
return flags;
|
|
}
|
|
|
|
static inline bool
|
|
case_option_single_p(OnigCaseFoldType flags, rb_encoding *enc, VALUE str)
|
|
{
|
|
if ((flags & ONIGENC_CASE_ASCII_ONLY) && (enc==rb_utf8_encoding() || rb_enc_mbmaxlen(enc) == 1))
|
|
return true;
|
|
return !(flags & ONIGENC_CASE_FOLD_TURKISH_AZERI) && ENC_CODERANGE(str) == ENC_CODERANGE_7BIT;
|
|
}
|
|
|
|
/* 16 should be long enough to absorb any kind of single character length increase */
|
|
#define CASE_MAPPING_ADDITIONAL_LENGTH 20
|
|
#ifndef CASEMAP_DEBUG
|
|
# define CASEMAP_DEBUG 0
|
|
#endif
|
|
|
|
struct mapping_buffer;
|
|
typedef struct mapping_buffer {
|
|
size_t capa;
|
|
size_t used;
|
|
struct mapping_buffer *next;
|
|
OnigUChar space[FLEX_ARY_LEN];
|
|
} mapping_buffer;
|
|
|
|
static void
|
|
mapping_buffer_free(void *p)
|
|
{
|
|
mapping_buffer *previous_buffer;
|
|
mapping_buffer *current_buffer = p;
|
|
while (current_buffer) {
|
|
previous_buffer = current_buffer;
|
|
current_buffer = current_buffer->next;
|
|
ruby_sized_xfree(previous_buffer, previous_buffer->capa);
|
|
}
|
|
}
|
|
|
|
static const rb_data_type_t mapping_buffer_type = {
|
|
"mapping_buffer",
|
|
{0, mapping_buffer_free,}
|
|
};
|
|
|
|
static VALUE
|
|
rb_str_casemap(VALUE source, OnigCaseFoldType *flags, rb_encoding *enc)
|
|
{
|
|
VALUE target;
|
|
|
|
const OnigUChar *source_current, *source_end;
|
|
int target_length = 0;
|
|
VALUE buffer_anchor;
|
|
mapping_buffer *current_buffer = 0;
|
|
mapping_buffer **pre_buffer;
|
|
size_t buffer_count = 0;
|
|
int buffer_length_or_invalid;
|
|
|
|
if (RSTRING_LEN(source) == 0) return str_duplicate(rb_cString, source);
|
|
|
|
source_current = (OnigUChar*)RSTRING_PTR(source);
|
|
source_end = (OnigUChar*)RSTRING_END(source);
|
|
|
|
buffer_anchor = TypedData_Wrap_Struct(0, &mapping_buffer_type, 0);
|
|
pre_buffer = (mapping_buffer **)&DATA_PTR(buffer_anchor);
|
|
while (source_current < source_end) {
|
|
/* increase multiplier using buffer count to converge quickly */
|
|
size_t capa = (size_t)(source_end-source_current)*++buffer_count + CASE_MAPPING_ADDITIONAL_LENGTH;
|
|
if (CASEMAP_DEBUG) {
|
|
fprintf(stderr, "Buffer allocation, capa is %"PRIuSIZE"\n", capa); /* for tuning */
|
|
}
|
|
current_buffer = xmalloc(offsetof(mapping_buffer, space) + capa);
|
|
*pre_buffer = current_buffer;
|
|
pre_buffer = ¤t_buffer->next;
|
|
current_buffer->next = NULL;
|
|
current_buffer->capa = capa;
|
|
buffer_length_or_invalid = enc->case_map(flags,
|
|
(const OnigUChar**)&source_current, source_end,
|
|
current_buffer->space,
|
|
current_buffer->space+current_buffer->capa,
|
|
enc);
|
|
if (buffer_length_or_invalid < 0) {
|
|
current_buffer = DATA_PTR(buffer_anchor);
|
|
DATA_PTR(buffer_anchor) = 0;
|
|
mapping_buffer_free(current_buffer);
|
|
rb_raise(rb_eArgError, "input string invalid");
|
|
}
|
|
target_length += current_buffer->used = buffer_length_or_invalid;
|
|
}
|
|
if (CASEMAP_DEBUG) {
|
|
fprintf(stderr, "Buffer count is %"PRIuSIZE"\n", buffer_count); /* for tuning */
|
|
}
|
|
|
|
if (buffer_count==1) {
|
|
target = rb_str_new((const char*)current_buffer->space, target_length);
|
|
}
|
|
else {
|
|
char *target_current;
|
|
|
|
target = rb_str_new(0, target_length);
|
|
target_current = RSTRING_PTR(target);
|
|
current_buffer = DATA_PTR(buffer_anchor);
|
|
while (current_buffer) {
|
|
memcpy(target_current, current_buffer->space, current_buffer->used);
|
|
target_current += current_buffer->used;
|
|
current_buffer = current_buffer->next;
|
|
}
|
|
}
|
|
current_buffer = DATA_PTR(buffer_anchor);
|
|
DATA_PTR(buffer_anchor) = 0;
|
|
mapping_buffer_free(current_buffer);
|
|
|
|
/* TODO: check about string terminator character */
|
|
str_enc_copy(target, source);
|
|
/*ENC_CODERANGE_SET(mapped, cr);*/
|
|
|
|
return target;
|
|
}
|
|
|
|
static VALUE
|
|
rb_str_ascii_casemap(VALUE source, VALUE target, OnigCaseFoldType *flags, rb_encoding *enc)
|
|
{
|
|
const OnigUChar *source_current, *source_end;
|
|
OnigUChar *target_current, *target_end;
|
|
long old_length = RSTRING_LEN(source);
|
|
int length_or_invalid;
|
|
|
|
if (old_length == 0) return Qnil;
|
|
|
|
source_current = (OnigUChar*)RSTRING_PTR(source);
|
|
source_end = (OnigUChar*)RSTRING_END(source);
|
|
if (source == target) {
|
|
target_current = (OnigUChar*)source_current;
|
|
target_end = (OnigUChar*)source_end;
|
|
}
|
|
else {
|
|
target_current = (OnigUChar*)RSTRING_PTR(target);
|
|
target_end = (OnigUChar*)RSTRING_END(target);
|
|
}
|
|
|
|
length_or_invalid = onigenc_ascii_only_case_map(flags,
|
|
&source_current, source_end,
|
|
target_current, target_end, enc);
|
|
if (length_or_invalid < 0)
|
|
rb_raise(rb_eArgError, "input string invalid");
|
|
if (CASEMAP_DEBUG && length_or_invalid != old_length) {
|
|
fprintf(stderr, "problem with rb_str_ascii_casemap"
|
|
"; old_length=%ld, new_length=%d\n", old_length, length_or_invalid);
|
|
rb_raise(rb_eArgError, "internal problem with rb_str_ascii_casemap"
|
|
"; old_length=%ld, new_length=%d\n", old_length, length_or_invalid);
|
|
}
|
|
|
|
str_enc_copy(target, source);
|
|
|
|
return target;
|
|
}
|
|
|
|
static bool
|
|
upcase_single(VALUE str)
|
|
{
|
|
char *s = RSTRING_PTR(str), *send = RSTRING_END(str);
|
|
bool modified = false;
|
|
|
|
while (s < send) {
|
|
unsigned int c = *(unsigned char*)s;
|
|
|
|
if (rb_enc_isascii(c, enc) && 'a' <= c && c <= 'z') {
|
|
*s = 'A' + (c - 'a');
|
|
modified = true;
|
|
}
|
|
s++;
|
|
}
|
|
return modified;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.upcase! -> str or nil
|
|
* str.upcase!([options]) -> str or nil
|
|
*
|
|
* Upcases the contents of <i>str</i>, returning <code>nil</code> if no changes
|
|
* were made.
|
|
*
|
|
* See String#downcase for meaning of +options+ and use with different encodings.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_upcase_bang(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
rb_encoding *enc;
|
|
OnigCaseFoldType flags = ONIGENC_CASE_UPCASE;
|
|
|
|
flags = check_case_options(argc, argv, flags);
|
|
str_modify_keep_cr(str);
|
|
enc = str_true_enc(str);
|
|
if (case_option_single_p(flags, enc, str)) {
|
|
if (upcase_single(str))
|
|
flags |= ONIGENC_CASE_MODIFIED;
|
|
}
|
|
else if (flags&ONIGENC_CASE_ASCII_ONLY)
|
|
rb_str_ascii_casemap(str, str, &flags, enc);
|
|
else
|
|
str_shared_replace(str, rb_str_casemap(str, &flags, enc));
|
|
|
|
if (ONIGENC_CASE_MODIFIED&flags) return str;
|
|
return Qnil;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.upcase -> new_str
|
|
* str.upcase([options]) -> new_str
|
|
*
|
|
* Returns a copy of <i>str</i> with all lowercase letters replaced with their
|
|
* uppercase counterparts.
|
|
*
|
|
* See String#downcase for meaning of +options+ and use with different encodings.
|
|
*
|
|
* "hEllO".upcase #=> "HELLO"
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_upcase(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
rb_encoding *enc;
|
|
OnigCaseFoldType flags = ONIGENC_CASE_UPCASE;
|
|
VALUE ret;
|
|
|
|
flags = check_case_options(argc, argv, flags);
|
|
enc = str_true_enc(str);
|
|
if (case_option_single_p(flags, enc, str)) {
|
|
ret = rb_str_new(RSTRING_PTR(str), RSTRING_LEN(str));
|
|
str_enc_copy(ret, str);
|
|
upcase_single(ret);
|
|
}
|
|
else if (flags&ONIGENC_CASE_ASCII_ONLY) {
|
|
ret = rb_str_new(0, RSTRING_LEN(str));
|
|
rb_str_ascii_casemap(str, ret, &flags, enc);
|
|
}
|
|
else {
|
|
ret = rb_str_casemap(str, &flags, enc);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool
|
|
downcase_single(VALUE str)
|
|
{
|
|
char *s = RSTRING_PTR(str), *send = RSTRING_END(str);
|
|
bool modified = false;
|
|
|
|
while (s < send) {
|
|
unsigned int c = *(unsigned char*)s;
|
|
|
|
if (rb_enc_isascii(c, enc) && 'A' <= c && c <= 'Z') {
|
|
*s = 'a' + (c - 'A');
|
|
modified = true;
|
|
}
|
|
s++;
|
|
}
|
|
|
|
return modified;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.downcase! -> str or nil
|
|
* str.downcase!([options]) -> str or nil
|
|
*
|
|
* Downcases the contents of <i>str</i>, returning <code>nil</code> if no
|
|
* changes were made.
|
|
*
|
|
* See String#downcase for meaning of +options+ and use with different encodings.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_downcase_bang(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
rb_encoding *enc;
|
|
OnigCaseFoldType flags = ONIGENC_CASE_DOWNCASE;
|
|
|
|
flags = check_case_options(argc, argv, flags);
|
|
str_modify_keep_cr(str);
|
|
enc = str_true_enc(str);
|
|
if (case_option_single_p(flags, enc, str)) {
|
|
if (downcase_single(str))
|
|
flags |= ONIGENC_CASE_MODIFIED;
|
|
}
|
|
else if (flags&ONIGENC_CASE_ASCII_ONLY)
|
|
rb_str_ascii_casemap(str, str, &flags, enc);
|
|
else
|
|
str_shared_replace(str, rb_str_casemap(str, &flags, enc));
|
|
|
|
if (ONIGENC_CASE_MODIFIED&flags) return str;
|
|
return Qnil;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.downcase -> new_str
|
|
* str.downcase([options]) -> new_str
|
|
*
|
|
* Returns a copy of <i>str</i> with all uppercase letters replaced with their
|
|
* lowercase counterparts. Which letters exactly are replaced, and by which
|
|
* other letters, depends on the presence or absence of options, and on the
|
|
* +encoding+ of the string.
|
|
*
|
|
* The meaning of the +options+ is as follows:
|
|
*
|
|
* No option ::
|
|
* Full Unicode case mapping, suitable for most languages
|
|
* (see :turkic and :lithuanian options below for exceptions).
|
|
* Context-dependent case mapping as described in Table 3-14 of the
|
|
* Unicode standard is currently not supported.
|
|
* :ascii ::
|
|
* Only the ASCII region, i.e. the characters ``A'' to ``Z'' and
|
|
* ``a'' to ``z'', are affected.
|
|
* This option cannot be combined with any other option.
|
|
* :turkic ::
|
|
* Full Unicode case mapping, adapted for Turkic languages
|
|
* (Turkish, Azerbaijani, ...). This means that upper case I is mapped to
|
|
* lower case dotless i, and so on.
|
|
* :lithuanian ::
|
|
* Currently, just full Unicode case mapping. In the future, full Unicode
|
|
* case mapping adapted for Lithuanian (keeping the dot on the lower case
|
|
* i even if there is an accent on top).
|
|
* :fold ::
|
|
* Only available on +downcase+ and +downcase!+. Unicode case <b>folding</b>,
|
|
* which is more far-reaching than Unicode case mapping.
|
|
* This option currently cannot be combined with any other option
|
|
* (i.e. there is currently no variant for turkic languages).
|
|
*
|
|
* Please note that several assumptions that are valid for ASCII-only case
|
|
* conversions do not hold for more general case conversions. For example,
|
|
* the length of the result may not be the same as the length of the input
|
|
* (neither in characters nor in bytes), some roundtrip assumptions
|
|
* (e.g. str.downcase == str.upcase.downcase) may not apply, and Unicode
|
|
* normalization (i.e. String#unicode_normalize) is not necessarily maintained
|
|
* by case mapping operations.
|
|
*
|
|
* Non-ASCII case mapping/folding is currently supported for UTF-8,
|
|
* UTF-16BE/LE, UTF-32BE/LE, and ISO-8859-1~16 Strings/Symbols.
|
|
* This support will be extended to other encodings.
|
|
*
|
|
* "hEllO".downcase #=> "hello"
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_downcase(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
rb_encoding *enc;
|
|
OnigCaseFoldType flags = ONIGENC_CASE_DOWNCASE;
|
|
VALUE ret;
|
|
|
|
flags = check_case_options(argc, argv, flags);
|
|
enc = str_true_enc(str);
|
|
if (case_option_single_p(flags, enc, str)) {
|
|
ret = rb_str_new(RSTRING_PTR(str), RSTRING_LEN(str));
|
|
str_enc_copy(ret, str);
|
|
downcase_single(ret);
|
|
}
|
|
else if (flags&ONIGENC_CASE_ASCII_ONLY) {
|
|
ret = rb_str_new(0, RSTRING_LEN(str));
|
|
rb_str_ascii_casemap(str, ret, &flags, enc);
|
|
}
|
|
else {
|
|
ret = rb_str_casemap(str, &flags, enc);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.capitalize! -> str or nil
|
|
* str.capitalize!([options]) -> str or nil
|
|
*
|
|
* Modifies <i>str</i> by converting the first character to uppercase and the
|
|
* remainder to lowercase. Returns <code>nil</code> if no changes are made.
|
|
* There is an exception for modern Georgian (mkhedruli/MTAVRULI), where
|
|
* the result is the same as for String#downcase, to avoid mixed case.
|
|
*
|
|
* See String#downcase for meaning of +options+ and use with different encodings.
|
|
*
|
|
* a = "hello"
|
|
* a.capitalize! #=> "Hello"
|
|
* a #=> "Hello"
|
|
* a.capitalize! #=> nil
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_capitalize_bang(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
rb_encoding *enc;
|
|
OnigCaseFoldType flags = ONIGENC_CASE_UPCASE | ONIGENC_CASE_TITLECASE;
|
|
|
|
flags = check_case_options(argc, argv, flags);
|
|
str_modify_keep_cr(str);
|
|
enc = str_true_enc(str);
|
|
if (RSTRING_LEN(str) == 0 || !RSTRING_PTR(str)) return Qnil;
|
|
if (flags&ONIGENC_CASE_ASCII_ONLY)
|
|
rb_str_ascii_casemap(str, str, &flags, enc);
|
|
else
|
|
str_shared_replace(str, rb_str_casemap(str, &flags, enc));
|
|
|
|
if (ONIGENC_CASE_MODIFIED&flags) return str;
|
|
return Qnil;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.capitalize -> new_str
|
|
* str.capitalize([options]) -> new_str
|
|
*
|
|
* Returns a copy of <i>str</i> with the first character converted to uppercase
|
|
* and the remainder to lowercase.
|
|
*
|
|
* See String#downcase for meaning of +options+ and use with different encodings.
|
|
*
|
|
* "hello".capitalize #=> "Hello"
|
|
* "HELLO".capitalize #=> "Hello"
|
|
* "123ABC".capitalize #=> "123abc"
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_capitalize(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
rb_encoding *enc;
|
|
OnigCaseFoldType flags = ONIGENC_CASE_UPCASE | ONIGENC_CASE_TITLECASE;
|
|
VALUE ret;
|
|
|
|
flags = check_case_options(argc, argv, flags);
|
|
enc = str_true_enc(str);
|
|
if (RSTRING_LEN(str) == 0 || !RSTRING_PTR(str)) return str;
|
|
if (flags&ONIGENC_CASE_ASCII_ONLY) {
|
|
ret = rb_str_new(0, RSTRING_LEN(str));
|
|
rb_str_ascii_casemap(str, ret, &flags, enc);
|
|
}
|
|
else {
|
|
ret = rb_str_casemap(str, &flags, enc);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.swapcase! -> str or nil
|
|
* str.swapcase!([options]) -> str or nil
|
|
*
|
|
* Equivalent to String#swapcase, but modifies the receiver in place,
|
|
* returning <i>str</i>, or <code>nil</code> if no changes were made.
|
|
*
|
|
* See String#downcase for meaning of +options+ and use with
|
|
* different encodings.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_swapcase_bang(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
rb_encoding *enc;
|
|
OnigCaseFoldType flags = ONIGENC_CASE_UPCASE | ONIGENC_CASE_DOWNCASE;
|
|
|
|
flags = check_case_options(argc, argv, flags);
|
|
str_modify_keep_cr(str);
|
|
enc = str_true_enc(str);
|
|
if (flags&ONIGENC_CASE_ASCII_ONLY)
|
|
rb_str_ascii_casemap(str, str, &flags, enc);
|
|
else
|
|
str_shared_replace(str, rb_str_casemap(str, &flags, enc));
|
|
|
|
if (ONIGENC_CASE_MODIFIED&flags) return str;
|
|
return Qnil;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.swapcase -> new_str
|
|
* str.swapcase([options]) -> new_str
|
|
*
|
|
* Returns a copy of <i>str</i> with uppercase alphabetic characters converted
|
|
* to lowercase and lowercase characters converted to uppercase.
|
|
*
|
|
* See String#downcase for meaning of +options+ and use with different encodings.
|
|
*
|
|
* "Hello".swapcase #=> "hELLO"
|
|
* "cYbEr_PuNk11".swapcase #=> "CyBeR_pUnK11"
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_swapcase(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
rb_encoding *enc;
|
|
OnigCaseFoldType flags = ONIGENC_CASE_UPCASE | ONIGENC_CASE_DOWNCASE;
|
|
VALUE ret;
|
|
|
|
flags = check_case_options(argc, argv, flags);
|
|
enc = str_true_enc(str);
|
|
if (RSTRING_LEN(str) == 0 || !RSTRING_PTR(str)) return str_duplicate(rb_cString, str);
|
|
if (flags&ONIGENC_CASE_ASCII_ONLY) {
|
|
ret = rb_str_new(0, RSTRING_LEN(str));
|
|
rb_str_ascii_casemap(str, ret, &flags, enc);
|
|
}
|
|
else {
|
|
ret = rb_str_casemap(str, &flags, enc);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
typedef unsigned char *USTR;
|
|
|
|
struct tr {
|
|
int gen;
|
|
unsigned int now, max;
|
|
char *p, *pend;
|
|
};
|
|
|
|
static unsigned int
|
|
trnext(struct tr *t, rb_encoding *enc)
|
|
{
|
|
int n;
|
|
|
|
for (;;) {
|
|
nextpart:
|
|
if (!t->gen) {
|
|
if (t->p == t->pend) return -1;
|
|
if (rb_enc_ascget(t->p, t->pend, &n, enc) == '\\' && t->p + n < t->pend) {
|
|
t->p += n;
|
|
}
|
|
t->now = rb_enc_codepoint_len(t->p, t->pend, &n, enc);
|
|
t->p += n;
|
|
if (rb_enc_ascget(t->p, t->pend, &n, enc) == '-' && t->p + n < t->pend) {
|
|
t->p += n;
|
|
if (t->p < t->pend) {
|
|
unsigned int c = rb_enc_codepoint_len(t->p, t->pend, &n, enc);
|
|
t->p += n;
|
|
if (t->now > c) {
|
|
if (t->now < 0x80 && c < 0x80) {
|
|
rb_raise(rb_eArgError,
|
|
"invalid range \"%c-%c\" in string transliteration",
|
|
t->now, c);
|
|
}
|
|
else {
|
|
rb_raise(rb_eArgError, "invalid range in string transliteration");
|
|
}
|
|
continue; /* not reached */
|
|
}
|
|
t->gen = 1;
|
|
t->max = c;
|
|
}
|
|
}
|
|
return t->now;
|
|
}
|
|
else {
|
|
while (ONIGENC_CODE_TO_MBCLEN(enc, ++t->now) <= 0) {
|
|
if (t->now == t->max) {
|
|
t->gen = 0;
|
|
goto nextpart;
|
|
}
|
|
}
|
|
if (t->now < t->max) {
|
|
return t->now;
|
|
}
|
|
else {
|
|
t->gen = 0;
|
|
return t->max;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static VALUE rb_str_delete_bang(int,VALUE*,VALUE);
|
|
|
|
static VALUE
|
|
tr_trans(VALUE str, VALUE src, VALUE repl, int sflag)
|
|
{
|
|
const unsigned int errc = -1;
|
|
unsigned int trans[256];
|
|
rb_encoding *enc, *e1, *e2;
|
|
struct tr trsrc, trrepl;
|
|
int cflag = 0;
|
|
unsigned int c, c0, last = 0;
|
|
int modify = 0, i, l;
|
|
unsigned char *s, *send;
|
|
VALUE hash = 0;
|
|
int singlebyte = single_byte_optimizable(str);
|
|
int termlen;
|
|
int cr;
|
|
|
|
#define CHECK_IF_ASCII(c) \
|
|
(void)((cr == ENC_CODERANGE_7BIT && !rb_isascii(c)) ? \
|
|
(cr = ENC_CODERANGE_VALID) : 0)
|
|
|
|
StringValue(src);
|
|
StringValue(repl);
|
|
if (RSTRING_LEN(str) == 0 || !RSTRING_PTR(str)) return Qnil;
|
|
if (RSTRING_LEN(repl) == 0) {
|
|
return rb_str_delete_bang(1, &src, str);
|
|
}
|
|
|
|
cr = ENC_CODERANGE(str);
|
|
e1 = rb_enc_check(str, src);
|
|
e2 = rb_enc_check(str, repl);
|
|
if (e1 == e2) {
|
|
enc = e1;
|
|
}
|
|
else {
|
|
enc = rb_enc_check(src, repl);
|
|
}
|
|
trsrc.p = RSTRING_PTR(src); trsrc.pend = trsrc.p + RSTRING_LEN(src);
|
|
if (RSTRING_LEN(src) > 1 &&
|
|
rb_enc_ascget(trsrc.p, trsrc.pend, &l, enc) == '^' &&
|
|
trsrc.p + l < trsrc.pend) {
|
|
cflag = 1;
|
|
trsrc.p += l;
|
|
}
|
|
trrepl.p = RSTRING_PTR(repl);
|
|
trrepl.pend = trrepl.p + RSTRING_LEN(repl);
|
|
trsrc.gen = trrepl.gen = 0;
|
|
trsrc.now = trrepl.now = 0;
|
|
trsrc.max = trrepl.max = 0;
|
|
|
|
if (cflag) {
|
|
for (i=0; i<256; i++) {
|
|
trans[i] = 1;
|
|
}
|
|
while ((c = trnext(&trsrc, enc)) != errc) {
|
|
if (c < 256) {
|
|
trans[c] = errc;
|
|
}
|
|
else {
|
|
if (!hash) hash = rb_hash_new();
|
|
rb_hash_aset(hash, UINT2NUM(c), Qtrue);
|
|
}
|
|
}
|
|
while ((c = trnext(&trrepl, enc)) != errc)
|
|
/* retrieve last replacer */;
|
|
last = trrepl.now;
|
|
for (i=0; i<256; i++) {
|
|
if (trans[i] != errc) {
|
|
trans[i] = last;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
unsigned int r;
|
|
|
|
for (i=0; i<256; i++) {
|
|
trans[i] = errc;
|
|
}
|
|
while ((c = trnext(&trsrc, enc)) != errc) {
|
|
r = trnext(&trrepl, enc);
|
|
if (r == errc) r = trrepl.now;
|
|
if (c < 256) {
|
|
trans[c] = r;
|
|
if (rb_enc_codelen(r, enc) != 1) singlebyte = 0;
|
|
}
|
|
else {
|
|
if (!hash) hash = rb_hash_new();
|
|
rb_hash_aset(hash, UINT2NUM(c), UINT2NUM(r));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cr == ENC_CODERANGE_VALID && rb_enc_asciicompat(e1))
|
|
cr = ENC_CODERANGE_7BIT;
|
|
str_modify_keep_cr(str);
|
|
s = (unsigned char *)RSTRING_PTR(str); send = (unsigned char *)RSTRING_END(str);
|
|
termlen = rb_enc_mbminlen(enc);
|
|
if (sflag) {
|
|
int clen, tlen;
|
|
long offset, max = RSTRING_LEN(str);
|
|
unsigned int save = -1;
|
|
unsigned char *buf = ALLOC_N(unsigned char, max + termlen), *t = buf;
|
|
|
|
while (s < send) {
|
|
int may_modify = 0;
|
|
|
|
c0 = c = rb_enc_codepoint_len((char *)s, (char *)send, &clen, e1);
|
|
tlen = enc == e1 ? clen : rb_enc_codelen(c, enc);
|
|
|
|
s += clen;
|
|
if (c < 256) {
|
|
c = trans[c];
|
|
}
|
|
else if (hash) {
|
|
VALUE tmp = rb_hash_lookup(hash, UINT2NUM(c));
|
|
if (NIL_P(tmp)) {
|
|
if (cflag) c = last;
|
|
else c = errc;
|
|
}
|
|
else if (cflag) c = errc;
|
|
else c = NUM2INT(tmp);
|
|
}
|
|
else {
|
|
c = errc;
|
|
}
|
|
if (c != (unsigned int)-1) {
|
|
if (save == c) {
|
|
CHECK_IF_ASCII(c);
|
|
continue;
|
|
}
|
|
save = c;
|
|
tlen = rb_enc_codelen(c, enc);
|
|
modify = 1;
|
|
}
|
|
else {
|
|
save = -1;
|
|
c = c0;
|
|
if (enc != e1) may_modify = 1;
|
|
}
|
|
if ((offset = t - buf) + tlen > max) {
|
|
size_t MAYBE_UNUSED(old) = max + termlen;
|
|
max = offset + tlen + (send - s);
|
|
SIZED_REALLOC_N(buf, unsigned char, max + termlen, old);
|
|
t = buf + offset;
|
|
}
|
|
rb_enc_mbcput(c, t, enc);
|
|
if (may_modify && memcmp(s, t, tlen) != 0) {
|
|
modify = 1;
|
|
}
|
|
CHECK_IF_ASCII(c);
|
|
t += tlen;
|
|
}
|
|
if (!STR_EMBED_P(str)) {
|
|
ruby_sized_xfree(STR_HEAP_PTR(str), STR_HEAP_SIZE(str));
|
|
}
|
|
TERM_FILL((char *)t, termlen);
|
|
RSTRING(str)->as.heap.ptr = (char *)buf;
|
|
RSTRING(str)->as.heap.len = t - buf;
|
|
STR_SET_NOEMBED(str);
|
|
RSTRING(str)->as.heap.aux.capa = max;
|
|
}
|
|
else if (rb_enc_mbmaxlen(enc) == 1 || (singlebyte && !hash)) {
|
|
while (s < send) {
|
|
c = (unsigned char)*s;
|
|
if (trans[c] != errc) {
|
|
if (!cflag) {
|
|
c = trans[c];
|
|
*s = c;
|
|
modify = 1;
|
|
}
|
|
else {
|
|
*s = last;
|
|
modify = 1;
|
|
}
|
|
}
|
|
CHECK_IF_ASCII(c);
|
|
s++;
|
|
}
|
|
}
|
|
else {
|
|
int clen, tlen;
|
|
long offset, max = (long)((send - s) * 1.2);
|
|
unsigned char *buf = ALLOC_N(unsigned char, max + termlen), *t = buf;
|
|
|
|
while (s < send) {
|
|
int may_modify = 0;
|
|
c0 = c = rb_enc_codepoint_len((char *)s, (char *)send, &clen, e1);
|
|
tlen = enc == e1 ? clen : rb_enc_codelen(c, enc);
|
|
|
|
if (c < 256) {
|
|
c = trans[c];
|
|
}
|
|
else if (hash) {
|
|
VALUE tmp = rb_hash_lookup(hash, UINT2NUM(c));
|
|
if (NIL_P(tmp)) {
|
|
if (cflag) c = last;
|
|
else c = errc;
|
|
}
|
|
else if (cflag) c = errc;
|
|
else c = NUM2INT(tmp);
|
|
}
|
|
else {
|
|
c = cflag ? last : errc;
|
|
}
|
|
if (c != errc) {
|
|
tlen = rb_enc_codelen(c, enc);
|
|
modify = 1;
|
|
}
|
|
else {
|
|
c = c0;
|
|
if (enc != e1) may_modify = 1;
|
|
}
|
|
if ((offset = t - buf) + tlen > max) {
|
|
size_t MAYBE_UNUSED(old) = max + termlen;
|
|
max = offset + tlen + (long)((send - s) * 1.2);
|
|
SIZED_REALLOC_N(buf, unsigned char, max + termlen, old);
|
|
t = buf + offset;
|
|
}
|
|
if (s != t) {
|
|
rb_enc_mbcput(c, t, enc);
|
|
if (may_modify && memcmp(s, t, tlen) != 0) {
|
|
modify = 1;
|
|
}
|
|
}
|
|
CHECK_IF_ASCII(c);
|
|
s += clen;
|
|
t += tlen;
|
|
}
|
|
if (!STR_EMBED_P(str)) {
|
|
ruby_sized_xfree(STR_HEAP_PTR(str), STR_HEAP_SIZE(str));
|
|
}
|
|
TERM_FILL((char *)t, termlen);
|
|
RSTRING(str)->as.heap.ptr = (char *)buf;
|
|
RSTRING(str)->as.heap.len = t - buf;
|
|
STR_SET_NOEMBED(str);
|
|
RSTRING(str)->as.heap.aux.capa = max;
|
|
}
|
|
|
|
if (modify) {
|
|
if (cr != ENC_CODERANGE_BROKEN)
|
|
ENC_CODERANGE_SET(str, cr);
|
|
rb_enc_associate(str, enc);
|
|
return str;
|
|
}
|
|
return Qnil;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.tr!(from_str, to_str) -> str or nil
|
|
*
|
|
* Translates <i>str</i> in place, using the same rules as
|
|
* String#tr. Returns <i>str</i>, or <code>nil</code> if no changes
|
|
* were made.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_tr_bang(VALUE str, VALUE src, VALUE repl)
|
|
{
|
|
return tr_trans(str, src, repl, 0);
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.tr(from_str, to_str) => new_str
|
|
*
|
|
* Returns a copy of +str+ with the characters in +from_str+ replaced by the
|
|
* corresponding characters in +to_str+. If +to_str+ is shorter than
|
|
* +from_str+, it is padded with its last character in order to maintain the
|
|
* correspondence.
|
|
*
|
|
* "hello".tr('el', 'ip') #=> "hippo"
|
|
* "hello".tr('aeiou', '*') #=> "h*ll*"
|
|
* "hello".tr('aeiou', 'AA*') #=> "hAll*"
|
|
*
|
|
* Both strings may use the <code>c1-c2</code> notation to denote ranges of
|
|
* characters, and +from_str+ may start with a <code>^</code>, which denotes
|
|
* all characters except those listed.
|
|
*
|
|
* "hello".tr('a-y', 'b-z') #=> "ifmmp"
|
|
* "hello".tr('^aeiou', '*') #=> "*e**o"
|
|
*
|
|
* The backslash character <code>\\</code> can be used to escape
|
|
* <code>^</code> or <code>-</code> and is otherwise ignored unless it
|
|
* appears at the end of a range or the end of the +from_str+ or +to_str+:
|
|
*
|
|
* "hello^world".tr("\\^aeiou", "*") #=> "h*ll**w*rld"
|
|
* "hello-world".tr("a\\-eo", "*") #=> "h*ll**w*rld"
|
|
*
|
|
* "hello\r\nworld".tr("\r", "") #=> "hello\nworld"
|
|
* "hello\r\nworld".tr("\\r", "") #=> "hello\r\nwold"
|
|
* "hello\r\nworld".tr("\\\r", "") #=> "hello\nworld"
|
|
*
|
|
* "X['\\b']".tr("X\\", "") #=> "['b']"
|
|
* "X['\\b']".tr("X-\\]", "") #=> "'b'"
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_tr(VALUE str, VALUE src, VALUE repl)
|
|
{
|
|
str = str_duplicate(rb_cString, str);
|
|
tr_trans(str, src, repl, 0);
|
|
return str;
|
|
}
|
|
|
|
#define TR_TABLE_MAX (UCHAR_MAX+1)
|
|
#define TR_TABLE_SIZE (TR_TABLE_MAX+1)
|
|
static void
|
|
tr_setup_table(VALUE str, char stable[TR_TABLE_SIZE], int first,
|
|
VALUE *tablep, VALUE *ctablep, rb_encoding *enc)
|
|
{
|
|
const unsigned int errc = -1;
|
|
char buf[TR_TABLE_MAX];
|
|
struct tr tr;
|
|
unsigned int c;
|
|
VALUE table = 0, ptable = 0;
|
|
int i, l, cflag = 0;
|
|
|
|
tr.p = RSTRING_PTR(str); tr.pend = tr.p + RSTRING_LEN(str);
|
|
tr.gen = tr.now = tr.max = 0;
|
|
|
|
if (RSTRING_LEN(str) > 1 && rb_enc_ascget(tr.p, tr.pend, &l, enc) == '^') {
|
|
cflag = 1;
|
|
tr.p += l;
|
|
}
|
|
if (first) {
|
|
for (i=0; i<TR_TABLE_MAX; i++) {
|
|
stable[i] = 1;
|
|
}
|
|
stable[TR_TABLE_MAX] = cflag;
|
|
}
|
|
else if (stable[TR_TABLE_MAX] && !cflag) {
|
|
stable[TR_TABLE_MAX] = 0;
|
|
}
|
|
for (i=0; i<TR_TABLE_MAX; i++) {
|
|
buf[i] = cflag;
|
|
}
|
|
|
|
while ((c = trnext(&tr, enc)) != errc) {
|
|
if (c < TR_TABLE_MAX) {
|
|
buf[(unsigned char)c] = !cflag;
|
|
}
|
|
else {
|
|
VALUE key = UINT2NUM(c);
|
|
|
|
if (!table && (first || *tablep || stable[TR_TABLE_MAX])) {
|
|
if (cflag) {
|
|
ptable = *ctablep;
|
|
table = ptable ? ptable : rb_hash_new();
|
|
*ctablep = table;
|
|
}
|
|
else {
|
|
table = rb_hash_new();
|
|
ptable = *tablep;
|
|
*tablep = table;
|
|
}
|
|
}
|
|
if (table && (!ptable || (cflag ^ !NIL_P(rb_hash_aref(ptable, key))))) {
|
|
rb_hash_aset(table, key, Qtrue);
|
|
}
|
|
}
|
|
}
|
|
for (i=0; i<TR_TABLE_MAX; i++) {
|
|
stable[i] = stable[i] && buf[i];
|
|
}
|
|
if (!table && !cflag) {
|
|
*tablep = 0;
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
tr_find(unsigned int c, const char table[TR_TABLE_SIZE], VALUE del, VALUE nodel)
|
|
{
|
|
if (c < TR_TABLE_MAX) {
|
|
return table[c] != 0;
|
|
}
|
|
else {
|
|
VALUE v = UINT2NUM(c);
|
|
|
|
if (del) {
|
|
if (!NIL_P(rb_hash_lookup(del, v)) &&
|
|
(!nodel || NIL_P(rb_hash_lookup(nodel, v)))) {
|
|
return TRUE;
|
|
}
|
|
}
|
|
else if (nodel && !NIL_P(rb_hash_lookup(nodel, v))) {
|
|
return FALSE;
|
|
}
|
|
return table[TR_TABLE_MAX] ? TRUE : FALSE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.delete!([other_str]+) -> str or nil
|
|
*
|
|
* Performs a <code>delete</code> operation in place, returning <i>str</i>, or
|
|
* <code>nil</code> if <i>str</i> was not modified.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_delete_bang(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
char squeez[TR_TABLE_SIZE];
|
|
rb_encoding *enc = 0;
|
|
char *s, *send, *t;
|
|
VALUE del = 0, nodel = 0;
|
|
int modify = 0;
|
|
int i, ascompat, cr;
|
|
|
|
if (RSTRING_LEN(str) == 0 || !RSTRING_PTR(str)) return Qnil;
|
|
rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
|
|
for (i=0; i<argc; i++) {
|
|
VALUE s = argv[i];
|
|
|
|
StringValue(s);
|
|
enc = rb_enc_check(str, s);
|
|
tr_setup_table(s, squeez, i==0, &del, &nodel, enc);
|
|
}
|
|
|
|
str_modify_keep_cr(str);
|
|
ascompat = rb_enc_asciicompat(enc);
|
|
s = t = RSTRING_PTR(str);
|
|
send = RSTRING_END(str);
|
|
cr = ascompat ? ENC_CODERANGE_7BIT : ENC_CODERANGE_VALID;
|
|
while (s < send) {
|
|
unsigned int c;
|
|
int clen;
|
|
|
|
if (ascompat && (c = *(unsigned char*)s) < 0x80) {
|
|
if (squeez[c]) {
|
|
modify = 1;
|
|
}
|
|
else {
|
|
if (t != s) *t = c;
|
|
t++;
|
|
}
|
|
s++;
|
|
}
|
|
else {
|
|
c = rb_enc_codepoint_len(s, send, &clen, enc);
|
|
|
|
if (tr_find(c, squeez, del, nodel)) {
|
|
modify = 1;
|
|
}
|
|
else {
|
|
if (t != s) rb_enc_mbcput(c, t, enc);
|
|
t += clen;
|
|
if (cr == ENC_CODERANGE_7BIT) cr = ENC_CODERANGE_VALID;
|
|
}
|
|
s += clen;
|
|
}
|
|
}
|
|
TERM_FILL(t, TERM_LEN(str));
|
|
STR_SET_LEN(str, t - RSTRING_PTR(str));
|
|
ENC_CODERANGE_SET(str, cr);
|
|
|
|
if (modify) return str;
|
|
return Qnil;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.delete([other_str]+) -> new_str
|
|
*
|
|
* Returns a copy of <i>str</i> with all characters in the intersection of its
|
|
* arguments deleted. Uses the same rules for building the set of characters as
|
|
* String#count.
|
|
*
|
|
* "hello".delete "l","lo" #=> "heo"
|
|
* "hello".delete "lo" #=> "he"
|
|
* "hello".delete "aeiou", "^e" #=> "hell"
|
|
* "hello".delete "ej-m" #=> "ho"
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_delete(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
str = str_duplicate(rb_cString, str);
|
|
rb_str_delete_bang(argc, argv, str);
|
|
return str;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.squeeze!([other_str]*) -> str or nil
|
|
*
|
|
* Squeezes <i>str</i> in place, returning either <i>str</i>, or
|
|
* <code>nil</code> if no changes were made.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_squeeze_bang(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
char squeez[TR_TABLE_SIZE];
|
|
rb_encoding *enc = 0;
|
|
VALUE del = 0, nodel = 0;
|
|
unsigned char *s, *send, *t;
|
|
int i, modify = 0;
|
|
int ascompat, singlebyte = single_byte_optimizable(str);
|
|
unsigned int save;
|
|
|
|
if (argc == 0) {
|
|
enc = STR_ENC_GET(str);
|
|
}
|
|
else {
|
|
for (i=0; i<argc; i++) {
|
|
VALUE s = argv[i];
|
|
|
|
StringValue(s);
|
|
enc = rb_enc_check(str, s);
|
|
if (singlebyte && !single_byte_optimizable(s))
|
|
singlebyte = 0;
|
|
tr_setup_table(s, squeez, i==0, &del, &nodel, enc);
|
|
}
|
|
}
|
|
|
|
str_modify_keep_cr(str);
|
|
s = t = (unsigned char *)RSTRING_PTR(str);
|
|
if (!s || RSTRING_LEN(str) == 0) return Qnil;
|
|
send = (unsigned char *)RSTRING_END(str);
|
|
save = -1;
|
|
ascompat = rb_enc_asciicompat(enc);
|
|
|
|
if (singlebyte) {
|
|
while (s < send) {
|
|
unsigned int c = *s++;
|
|
if (c != save || (argc > 0 && !squeez[c])) {
|
|
*t++ = save = c;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
while (s < send) {
|
|
unsigned int c;
|
|
int clen;
|
|
|
|
if (ascompat && (c = *s) < 0x80) {
|
|
if (c != save || (argc > 0 && !squeez[c])) {
|
|
*t++ = save = c;
|
|
}
|
|
s++;
|
|
}
|
|
else {
|
|
c = rb_enc_codepoint_len((char *)s, (char *)send, &clen, enc);
|
|
|
|
if (c != save || (argc > 0 && !tr_find(c, squeez, del, nodel))) {
|
|
if (t != s) rb_enc_mbcput(c, t, enc);
|
|
save = c;
|
|
t += clen;
|
|
}
|
|
s += clen;
|
|
}
|
|
}
|
|
}
|
|
|
|
TERM_FILL((char *)t, TERM_LEN(str));
|
|
if ((char *)t - RSTRING_PTR(str) != RSTRING_LEN(str)) {
|
|
STR_SET_LEN(str, (char *)t - RSTRING_PTR(str));
|
|
modify = 1;
|
|
}
|
|
|
|
if (modify) return str;
|
|
return Qnil;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.squeeze([other_str]*) -> new_str
|
|
*
|
|
* Builds a set of characters from the <i>other_str</i> parameter(s)
|
|
* using the procedure described for String#count. Returns a new
|
|
* string where runs of the same character that occur in this set are
|
|
* replaced by a single character. If no arguments are given, all
|
|
* runs of identical characters are replaced by a single character.
|
|
*
|
|
* "yellow moon".squeeze #=> "yelow mon"
|
|
* " now is the".squeeze(" ") #=> " now is the"
|
|
* "putters shoot balls".squeeze("m-z") #=> "puters shot balls"
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_squeeze(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
str = str_duplicate(rb_cString, str);
|
|
rb_str_squeeze_bang(argc, argv, str);
|
|
return str;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.tr_s!(from_str, to_str) -> str or nil
|
|
*
|
|
* Performs String#tr_s processing on <i>str</i> in place,
|
|
* returning <i>str</i>, or <code>nil</code> if no changes were made.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_tr_s_bang(VALUE str, VALUE src, VALUE repl)
|
|
{
|
|
return tr_trans(str, src, repl, 1);
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.tr_s(from_str, to_str) -> new_str
|
|
*
|
|
* Processes a copy of <i>str</i> as described under String#tr, then
|
|
* removes duplicate characters in regions that were affected by the
|
|
* translation.
|
|
*
|
|
* "hello".tr_s('l', 'r') #=> "hero"
|
|
* "hello".tr_s('el', '*') #=> "h*o"
|
|
* "hello".tr_s('el', 'hx') #=> "hhxo"
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_tr_s(VALUE str, VALUE src, VALUE repl)
|
|
{
|
|
str = str_duplicate(rb_cString, str);
|
|
tr_trans(str, src, repl, 1);
|
|
return str;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.count([other_str]+) -> integer
|
|
*
|
|
* Each +other_str+ parameter defines a set of characters to count. The
|
|
* intersection of these sets defines the characters to count in +str+. Any
|
|
* +other_str+ that starts with a caret <code>^</code> is negated. The
|
|
* sequence <code>c1-c2</code> means all characters between c1 and c2. The
|
|
* backslash character <code>\\</code> can be used to escape <code>^</code> or
|
|
* <code>-</code> and is otherwise ignored unless it appears at the end of a
|
|
* sequence or the end of a +other_str+.
|
|
*
|
|
* 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
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_count(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
char table[TR_TABLE_SIZE];
|
|
rb_encoding *enc = 0;
|
|
VALUE del = 0, nodel = 0, tstr;
|
|
char *s, *send;
|
|
int i;
|
|
int ascompat;
|
|
size_t n = 0;
|
|
|
|
rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
|
|
|
|
tstr = argv[0];
|
|
StringValue(tstr);
|
|
enc = rb_enc_check(str, tstr);
|
|
if (argc == 1) {
|
|
const char *ptstr;
|
|
if (RSTRING_LEN(tstr) == 1 && rb_enc_asciicompat(enc) &&
|
|
(ptstr = RSTRING_PTR(tstr),
|
|
ONIGENC_IS_ALLOWED_REVERSE_MATCH(enc, (const unsigned char *)ptstr, (const unsigned char *)ptstr+1)) &&
|
|
!is_broken_string(str)) {
|
|
int clen;
|
|
unsigned char c = rb_enc_codepoint_len(ptstr, ptstr+1, &clen, enc);
|
|
|
|
s = RSTRING_PTR(str);
|
|
if (!s || RSTRING_LEN(str) == 0) return INT2FIX(0);
|
|
send = RSTRING_END(str);
|
|
while (s < send) {
|
|
if (*(unsigned char*)s++ == c) n++;
|
|
}
|
|
return SIZET2NUM(n);
|
|
}
|
|
}
|
|
|
|
tr_setup_table(tstr, table, TRUE, &del, &nodel, enc);
|
|
for (i=1; i<argc; i++) {
|
|
tstr = argv[i];
|
|
StringValue(tstr);
|
|
enc = rb_enc_check(str, tstr);
|
|
tr_setup_table(tstr, table, FALSE, &del, &nodel, enc);
|
|
}
|
|
|
|
s = RSTRING_PTR(str);
|
|
if (!s || RSTRING_LEN(str) == 0) return INT2FIX(0);
|
|
send = RSTRING_END(str);
|
|
ascompat = rb_enc_asciicompat(enc);
|
|
while (s < send) {
|
|
unsigned int c;
|
|
|
|
if (ascompat && (c = *(unsigned char*)s) < 0x80) {
|
|
if (table[c]) {
|
|
n++;
|
|
}
|
|
s++;
|
|
}
|
|
else {
|
|
int clen;
|
|
c = rb_enc_codepoint_len(s, send, &clen, enc);
|
|
if (tr_find(c, table, del, nodel)) {
|
|
n++;
|
|
}
|
|
s += clen;
|
|
}
|
|
}
|
|
|
|
return SIZET2NUM(n);
|
|
}
|
|
|
|
static VALUE
|
|
rb_fs_check(VALUE val)
|
|
{
|
|
if (!NIL_P(val) && !RB_TYPE_P(val, T_STRING) && !RB_TYPE_P(val, T_REGEXP)) {
|
|
val = rb_check_string_type(val);
|
|
if (NIL_P(val)) return 0;
|
|
}
|
|
return val;
|
|
}
|
|
|
|
static const char isspacetable[256] = {
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
|
};
|
|
|
|
#define ascii_isspace(c) isspacetable[(unsigned char)(c)]
|
|
|
|
static long
|
|
split_string(VALUE result, VALUE str, long beg, long len, long empty_count)
|
|
{
|
|
if (empty_count >= 0 && len == 0) {
|
|
return empty_count + 1;
|
|
}
|
|
if (empty_count > 0) {
|
|
/* make different substrings */
|
|
if (result) {
|
|
do {
|
|
rb_ary_push(result, str_new_empty_String(str));
|
|
} while (--empty_count > 0);
|
|
}
|
|
else {
|
|
do {
|
|
rb_yield(str_new_empty_String(str));
|
|
} while (--empty_count > 0);
|
|
}
|
|
}
|
|
str = rb_str_subseq(str, beg, len);
|
|
if (result) {
|
|
rb_ary_push(result, str);
|
|
}
|
|
else {
|
|
rb_yield(str);
|
|
}
|
|
return empty_count;
|
|
}
|
|
|
|
typedef enum {
|
|
SPLIT_TYPE_AWK, SPLIT_TYPE_STRING, SPLIT_TYPE_REGEXP, SPLIT_TYPE_CHARS
|
|
} split_type_t;
|
|
|
|
static split_type_t
|
|
literal_split_pattern(VALUE spat, split_type_t default_type)
|
|
{
|
|
rb_encoding *enc = STR_ENC_GET(spat);
|
|
const char *ptr;
|
|
long len;
|
|
RSTRING_GETMEM(spat, ptr, len);
|
|
if (len == 0) {
|
|
/* Special case - split into chars */
|
|
return SPLIT_TYPE_CHARS;
|
|
}
|
|
else if (rb_enc_asciicompat(enc)) {
|
|
if (len == 1 && ptr[0] == ' ') {
|
|
return SPLIT_TYPE_AWK;
|
|
}
|
|
}
|
|
else {
|
|
int l;
|
|
if (rb_enc_ascget(ptr, ptr + len, &l, enc) == ' ' && len == l) {
|
|
return SPLIT_TYPE_AWK;
|
|
}
|
|
}
|
|
return default_type;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.split(pattern=nil, [limit]) -> an_array
|
|
* str.split(pattern=nil, [limit]) {|sub| block } -> str
|
|
*
|
|
* Divides <i>str</i> into substrings based on a delimiter, returning an array
|
|
* of these substrings.
|
|
*
|
|
* If <i>pattern</i> is a String, then its contents are used as
|
|
* the delimiter when splitting <i>str</i>. If <i>pattern</i> is a single
|
|
* space, <i>str</i> is split on whitespace, with leading and trailing
|
|
* whitespace and runs of contiguous whitespace characters ignored.
|
|
*
|
|
* If <i>pattern</i> is a Regexp, <i>str</i> is divided where the
|
|
* pattern matches. Whenever the pattern matches a zero-length string,
|
|
* <i>str</i> is split into individual characters. If <i>pattern</i> contains
|
|
* groups, the respective matches will be returned in the array as well.
|
|
*
|
|
* If <i>pattern</i> is <code>nil</code>, the value of <code>$;</code> is used.
|
|
* If <code>$;</code> is <code>nil</code> (which is the default), <i>str</i> is
|
|
* split on whitespace as if ' ' were specified.
|
|
*
|
|
* If the <i>limit</i> parameter is omitted, trailing null fields are
|
|
* suppressed. If <i>limit</i> is a positive number, at most that number
|
|
* of split substrings will be returned (captured groups will be returned
|
|
* as well, but are not counted towards the limit).
|
|
* If <i>limit</i> is <code>1</code>, the entire
|
|
* string is returned as the only entry in an array. If negative, there is no
|
|
* limit to the number of fields returned, and trailing null fields are not
|
|
* suppressed.
|
|
*
|
|
* When the input +str+ is empty an empty Array is returned as the string is
|
|
* considered to have no fields to split.
|
|
*
|
|
* " now's the time ".split #=> ["now's", "the", "time"]
|
|
* " now's the time ".split(' ') #=> ["now's", "the", "time"]
|
|
* " now's the time".split(/ /) #=> ["", "now's", "", "the", "time"]
|
|
* "1, 2.34,56, 7".split(%r{,\s*}) #=> ["1", "2.34", "56", "7"]
|
|
* "hello".split(//) #=> ["h", "e", "l", "l", "o"]
|
|
* "hello".split(//, 3) #=> ["h", "e", "llo"]
|
|
* "hi mom".split(%r{\s*}) #=> ["h", "i", "m", "o", "m"]
|
|
*
|
|
* "mellow yellow".split("ello") #=> ["m", "w y", "w"]
|
|
* "1,2,,3,4,,".split(',') #=> ["1", "2", "", "3", "4"]
|
|
* "1,2,,3,4,,".split(',', 4) #=> ["1", "2", "", "3,4,,"]
|
|
* "1,2,,3,4,,".split(',', -4) #=> ["1", "2", "", "3", "4", "", ""]
|
|
*
|
|
* "1:2:3".split(/(:)()()/, 2) #=> ["1", ":", "", "", "2:3"]
|
|
*
|
|
* "".split(',', -1) #=> []
|
|
*
|
|
* If a block is given, invoke the block with each split substring.
|
|
*
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_split_m(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
rb_encoding *enc;
|
|
VALUE spat;
|
|
VALUE limit;
|
|
split_type_t split_type;
|
|
long beg, end, i = 0, empty_count = -1;
|
|
int lim = 0;
|
|
VALUE result, tmp;
|
|
|
|
result = rb_block_given_p() ? Qfalse : Qnil;
|
|
if (rb_scan_args(argc, argv, "02", &spat, &limit) == 2) {
|
|
lim = NUM2INT(limit);
|
|
if (lim <= 0) limit = Qnil;
|
|
else if (lim == 1) {
|
|
if (RSTRING_LEN(str) == 0)
|
|
return result ? rb_ary_new2(0) : str;
|
|
tmp = str_duplicate(rb_cString, str);
|
|
if (!result) {
|
|
rb_yield(tmp);
|
|
return str;
|
|
}
|
|
return rb_ary_new3(1, tmp);
|
|
}
|
|
i = 1;
|
|
}
|
|
if (NIL_P(limit) && !lim) empty_count = 0;
|
|
|
|
enc = STR_ENC_GET(str);
|
|
split_type = SPLIT_TYPE_REGEXP;
|
|
if (!NIL_P(spat)) {
|
|
spat = get_pat_quoted(spat, 0);
|
|
}
|
|
else if (NIL_P(spat = rb_fs)) {
|
|
split_type = SPLIT_TYPE_AWK;
|
|
}
|
|
else if (!(spat = rb_fs_check(spat))) {
|
|
rb_raise(rb_eTypeError, "value of $; must be String or Regexp");
|
|
}
|
|
else {
|
|
rb_category_warn(RB_WARN_CATEGORY_DEPRECATED, "$; is set to non-nil value");
|
|
}
|
|
if (split_type != SPLIT_TYPE_AWK) {
|
|
switch (BUILTIN_TYPE(spat)) {
|
|
case T_REGEXP:
|
|
rb_reg_options(spat); /* check if uninitialized */
|
|
tmp = RREGEXP_SRC(spat);
|
|
split_type = literal_split_pattern(tmp, SPLIT_TYPE_REGEXP);
|
|
if (split_type == SPLIT_TYPE_AWK) {
|
|
spat = tmp;
|
|
split_type = SPLIT_TYPE_STRING;
|
|
}
|
|
break;
|
|
|
|
case T_STRING:
|
|
mustnot_broken(spat);
|
|
split_type = literal_split_pattern(spat, SPLIT_TYPE_STRING);
|
|
break;
|
|
|
|
default:
|
|
UNREACHABLE_RETURN(Qnil);
|
|
}
|
|
}
|
|
|
|
#define SPLIT_STR(beg, len) (empty_count = split_string(result, str, beg, len, empty_count))
|
|
|
|
if (result) result = rb_ary_new();
|
|
beg = 0;
|
|
char *ptr = RSTRING_PTR(str);
|
|
char *eptr = RSTRING_END(str);
|
|
if (split_type == SPLIT_TYPE_AWK) {
|
|
char *bptr = ptr;
|
|
int skip = 1;
|
|
unsigned int c;
|
|
|
|
end = beg;
|
|
if (is_ascii_string(str)) {
|
|
while (ptr < eptr) {
|
|
c = (unsigned char)*ptr++;
|
|
if (skip) {
|
|
if (ascii_isspace(c)) {
|
|
beg = ptr - bptr;
|
|
}
|
|
else {
|
|
end = ptr - bptr;
|
|
skip = 0;
|
|
if (!NIL_P(limit) && lim <= i) break;
|
|
}
|
|
}
|
|
else if (ascii_isspace(c)) {
|
|
SPLIT_STR(beg, end-beg);
|
|
skip = 1;
|
|
beg = ptr - bptr;
|
|
if (!NIL_P(limit)) ++i;
|
|
}
|
|
else {
|
|
end = ptr - bptr;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
while (ptr < eptr) {
|
|
int n;
|
|
|
|
c = rb_enc_codepoint_len(ptr, eptr, &n, enc);
|
|
ptr += n;
|
|
if (skip) {
|
|
if (rb_isspace(c)) {
|
|
beg = ptr - bptr;
|
|
}
|
|
else {
|
|
end = ptr - bptr;
|
|
skip = 0;
|
|
if (!NIL_P(limit) && lim <= i) break;
|
|
}
|
|
}
|
|
else if (rb_isspace(c)) {
|
|
SPLIT_STR(beg, end-beg);
|
|
skip = 1;
|
|
beg = ptr - bptr;
|
|
if (!NIL_P(limit)) ++i;
|
|
}
|
|
else {
|
|
end = ptr - bptr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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);
|
|
|
|
mustnot_broken(str);
|
|
enc = rb_enc_check(str, spat);
|
|
while (ptr < eptr &&
|
|
(end = rb_memsearch(sptr, slen, ptr, eptr - ptr, enc)) >= 0) {
|
|
/* Check we are at the start of a char */
|
|
char *t = rb_enc_right_char_head(ptr, ptr + end, eptr, enc);
|
|
if (t != ptr + end) {
|
|
ptr = t;
|
|
continue;
|
|
}
|
|
SPLIT_STR(substr_start - str_start, (ptr+end) - substr_start);
|
|
ptr += end + slen;
|
|
substr_start = ptr;
|
|
if (!NIL_P(limit) && lim <= ++i) break;
|
|
}
|
|
beg = ptr - str_start;
|
|
}
|
|
else if (split_type == SPLIT_TYPE_CHARS) {
|
|
char *str_start = ptr;
|
|
int n;
|
|
|
|
mustnot_broken(str);
|
|
enc = rb_enc_get(str);
|
|
while (ptr < eptr &&
|
|
(n = rb_enc_precise_mbclen(ptr, eptr, enc)) > 0) {
|
|
SPLIT_STR(ptr - str_start, n);
|
|
ptr += n;
|
|
if (!NIL_P(limit) && lim <= ++i) break;
|
|
}
|
|
beg = ptr - str_start;
|
|
}
|
|
else {
|
|
long len = RSTRING_LEN(str);
|
|
long start = beg;
|
|
long idx;
|
|
int last_null = 0;
|
|
struct re_registers *regs;
|
|
VALUE match = 0;
|
|
|
|
for (; rb_reg_search(spat, str, start, 0) >= 0;
|
|
(match ? (rb_match_unbusy(match), rb_backref_set(match)) : (void)0)) {
|
|
match = rb_backref_get();
|
|
if (!result) rb_match_busy(match);
|
|
regs = RMATCH_REGS(match);
|
|
end = BEG(0);
|
|
if (start == end && BEG(0) == END(0)) {
|
|
if (!ptr) {
|
|
SPLIT_STR(0, 0);
|
|
break;
|
|
}
|
|
else if (last_null == 1) {
|
|
SPLIT_STR(beg, rb_enc_fast_mbclen(ptr+beg, eptr, enc));
|
|
beg = start;
|
|
}
|
|
else {
|
|
if (start == len)
|
|
start++;
|
|
else
|
|
start += rb_enc_fast_mbclen(ptr+start,eptr,enc);
|
|
last_null = 1;
|
|
continue;
|
|
}
|
|
}
|
|
else {
|
|
SPLIT_STR(beg, end-beg);
|
|
beg = start = END(0);
|
|
}
|
|
last_null = 0;
|
|
|
|
for (idx=1; idx < regs->num_regs; idx++) {
|
|
if (BEG(idx) == -1) continue;
|
|
SPLIT_STR(BEG(idx), END(idx)-BEG(idx));
|
|
}
|
|
if (!NIL_P(limit) && lim <= ++i) break;
|
|
}
|
|
if (match) rb_match_unbusy(match);
|
|
}
|
|
if (RSTRING_LEN(str) > 0 && (!NIL_P(limit) || RSTRING_LEN(str) > beg || lim < 0)) {
|
|
SPLIT_STR(beg, RSTRING_LEN(str)-beg);
|
|
}
|
|
|
|
return result ? result : str;
|
|
}
|
|
|
|
VALUE
|
|
rb_str_split(VALUE str, const char *sep0)
|
|
{
|
|
VALUE sep;
|
|
|
|
StringValue(str);
|
|
sep = rb_str_new_cstr(sep0);
|
|
return rb_str_split_m(1, &sep, str);
|
|
}
|
|
|
|
#define WANTARRAY(m, size) (!rb_block_given_p() ? rb_ary_new_capa(size) : 0)
|
|
|
|
static inline int
|
|
enumerator_element(VALUE ary, VALUE e)
|
|
{
|
|
if (ary) {
|
|
rb_ary_push(ary, e);
|
|
return 0;
|
|
}
|
|
else {
|
|
rb_yield(e);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
#define ENUM_ELEM(ary, e) enumerator_element(ary, e)
|
|
|
|
static const char *
|
|
chomp_newline(const char *p, const char *e, rb_encoding *enc)
|
|
{
|
|
const char *prev = rb_enc_prev_char(p, e, e, enc);
|
|
if (rb_enc_is_newline(prev, e, enc)) {
|
|
e = prev;
|
|
prev = rb_enc_prev_char(p, e, e, enc);
|
|
if (prev && rb_enc_ascget(prev, e, NULL, enc) == '\r')
|
|
e = prev;
|
|
}
|
|
return e;
|
|
}
|
|
|
|
static VALUE
|
|
get_rs(void)
|
|
{
|
|
VALUE rs = rb_rs;
|
|
if (!NIL_P(rs) &&
|
|
(!RB_TYPE_P(rs, T_STRING) ||
|
|
RSTRING_LEN(rs) != 1 ||
|
|
RSTRING_PTR(rs)[0] != '\n')) {
|
|
rb_category_warn(RB_WARN_CATEGORY_DEPRECATED, "$/ is set to non-default value");
|
|
}
|
|
return rs;
|
|
}
|
|
|
|
#define rb_rs get_rs()
|
|
|
|
static VALUE
|
|
rb_str_enumerate_lines(int argc, VALUE *argv, VALUE str, VALUE ary)
|
|
{
|
|
rb_encoding *enc;
|
|
VALUE line, rs, orig = str, opts = Qnil, chomp = Qfalse;
|
|
const char *ptr, *pend, *subptr, *subend, *rsptr, *hit, *adjusted;
|
|
long pos, len, rslen;
|
|
int rsnewline = 0;
|
|
|
|
if (rb_scan_args(argc, argv, "01:", &rs, &opts) == 0)
|
|
rs = rb_rs;
|
|
if (!NIL_P(opts)) {
|
|
static ID keywords[1];
|
|
if (!keywords[0]) {
|
|
keywords[0] = rb_intern_const("chomp");
|
|
}
|
|
rb_get_kwargs(opts, keywords, 0, 1, &chomp);
|
|
chomp = (chomp != Qundef && RTEST(chomp));
|
|
}
|
|
|
|
if (NIL_P(rs)) {
|
|
if (!ENUM_ELEM(ary, str)) {
|
|
return ary;
|
|
}
|
|
else {
|
|
return orig;
|
|
}
|
|
}
|
|
|
|
if (!RSTRING_LEN(str)) goto end;
|
|
str = rb_str_new_frozen(str);
|
|
ptr = subptr = RSTRING_PTR(str);
|
|
pend = RSTRING_END(str);
|
|
len = RSTRING_LEN(str);
|
|
StringValue(rs);
|
|
rslen = RSTRING_LEN(rs);
|
|
|
|
if (rs == rb_default_rs)
|
|
enc = rb_enc_get(str);
|
|
else
|
|
enc = rb_enc_check(str, rs);
|
|
|
|
if (rslen == 0) {
|
|
/* paragraph mode */
|
|
int n;
|
|
const char *eol = NULL;
|
|
subend = subptr;
|
|
while (subend < pend) {
|
|
do {
|
|
if (rb_enc_ascget(subend, pend, &n, enc) != '\r')
|
|
n = 0;
|
|
rslen = n + rb_enc_mbclen(subend + n, pend, enc);
|
|
if (rb_enc_is_newline(subend + n, pend, enc)) {
|
|
if (eol == subend) break;
|
|
subend += rslen;
|
|
if (subptr) eol = subend;
|
|
}
|
|
else {
|
|
if (!subptr) subptr = subend;
|
|
subend += rslen;
|
|
}
|
|
rslen = 0;
|
|
} while (subend < pend);
|
|
if (!subptr) break;
|
|
line = rb_str_subseq(str, subptr - ptr,
|
|
subend - subptr + (chomp ? 0 : rslen));
|
|
if (ENUM_ELEM(ary, line)) {
|
|
str_mod_check(str, ptr, len);
|
|
}
|
|
subptr = eol = NULL;
|
|
}
|
|
goto end;
|
|
}
|
|
else {
|
|
rsptr = RSTRING_PTR(rs);
|
|
if (RSTRING_LEN(rs) == rb_enc_mbminlen(enc) &&
|
|
rb_enc_is_newline(rsptr, rsptr + RSTRING_LEN(rs), enc)) {
|
|
rsnewline = 1;
|
|
}
|
|
}
|
|
|
|
if ((rs == rb_default_rs) && !rb_enc_asciicompat(enc)) {
|
|
rs = rb_str_new(rsptr, rslen);
|
|
rs = rb_str_encode(rs, rb_enc_from_encoding(enc), 0, Qnil);
|
|
rsptr = RSTRING_PTR(rs);
|
|
rslen = RSTRING_LEN(rs);
|
|
}
|
|
|
|
while (subptr < pend) {
|
|
pos = rb_memsearch(rsptr, rslen, subptr, pend - subptr, enc);
|
|
if (pos < 0) break;
|
|
hit = subptr + pos;
|
|
adjusted = rb_enc_right_char_head(subptr, hit, pend, enc);
|
|
if (hit != adjusted) {
|
|
subptr = adjusted;
|
|
continue;
|
|
}
|
|
subend = hit += rslen;
|
|
if (chomp) {
|
|
if (rsnewline) {
|
|
subend = chomp_newline(subptr, subend, enc);
|
|
}
|
|
else {
|
|
subend -= rslen;
|
|
}
|
|
}
|
|
line = rb_str_subseq(str, subptr - ptr, subend - subptr);
|
|
if (ENUM_ELEM(ary, line)) {
|
|
str_mod_check(str, ptr, len);
|
|
}
|
|
subptr = hit;
|
|
}
|
|
|
|
if (subptr != pend) {
|
|
if (chomp) {
|
|
if (rsnewline) {
|
|
pend = chomp_newline(subptr, pend, enc);
|
|
}
|
|
else if (pend - subptr >= rslen &&
|
|
memcmp(pend - rslen, rsptr, rslen) == 0) {
|
|
pend -= rslen;
|
|
}
|
|
}
|
|
line = rb_str_subseq(str, subptr - ptr, pend - subptr);
|
|
ENUM_ELEM(ary, line);
|
|
RB_GC_GUARD(str);
|
|
}
|
|
|
|
end:
|
|
if (ary)
|
|
return ary;
|
|
else
|
|
return orig;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.each_line(separator=$/, chomp: false) {|substr| block } -> str
|
|
* str.each_line(separator=$/, chomp: false) -> an_enumerator
|
|
*
|
|
* Splits <i>str</i> using the supplied parameter as the record
|
|
* separator (<code>$/</code> by default), passing each substring in
|
|
* turn to the supplied block. If a zero-length record separator is
|
|
* supplied, the string is split into paragraphs delimited by
|
|
* multiple successive newlines.
|
|
*
|
|
* If +chomp+ is +true+, +separator+ will be removed from the end of each
|
|
* line.
|
|
*
|
|
* If no block is given, an enumerator is returned instead.
|
|
*
|
|
* "hello\nworld".each_line {|s| p s}
|
|
* # prints:
|
|
* # "hello\n"
|
|
* # "world"
|
|
*
|
|
* "hello\nworld".each_line('l') {|s| p s}
|
|
* # prints:
|
|
* # "hel"
|
|
* # "l"
|
|
* # "o\nworl"
|
|
* # "d"
|
|
*
|
|
* "hello\n\n\nworld".each_line('') {|s| p s}
|
|
* # prints
|
|
* # "hello\n\n"
|
|
* # "world"
|
|
*
|
|
* "hello\nworld".each_line(chomp: true) {|s| p s}
|
|
* # prints:
|
|
* # "hello"
|
|
* # "world"
|
|
*
|
|
* "hello\nworld".each_line('l', chomp: true) {|s| p s}
|
|
* # prints:
|
|
* # "he"
|
|
* # ""
|
|
* # "o\nwor"
|
|
* # "d"
|
|
*
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_each_line(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
RETURN_SIZED_ENUMERATOR(str, argc, argv, 0);
|
|
return rb_str_enumerate_lines(argc, argv, str, 0);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.lines(separator=$/, chomp: false) -> an_array
|
|
*
|
|
* Returns an array of lines in <i>str</i> split using the supplied
|
|
* record separator (<code>$/</code> by default). This is a
|
|
* shorthand for <code>str.each_line(separator, getline_args).to_a</code>.
|
|
*
|
|
* If +chomp+ is +true+, +separator+ will be removed from the end of each
|
|
* line.
|
|
*
|
|
* "hello\nworld\n".lines #=> ["hello\n", "world\n"]
|
|
* "hello world".lines(' ') #=> ["hello ", " ", "world"]
|
|
* "hello\nworld\n".lines(chomp: true) #=> ["hello", "world"]
|
|
*
|
|
* If a block is given, which is a deprecated form, works the same as
|
|
* <code>each_line</code>.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_lines(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
VALUE ary = WANTARRAY("lines", 0);
|
|
return rb_str_enumerate_lines(argc, argv, str, ary);
|
|
}
|
|
|
|
static VALUE
|
|
rb_str_each_byte_size(VALUE str, VALUE args, VALUE eobj)
|
|
{
|
|
return LONG2FIX(RSTRING_LEN(str));
|
|
}
|
|
|
|
static VALUE
|
|
rb_str_enumerate_bytes(VALUE str, VALUE ary)
|
|
{
|
|
long i;
|
|
|
|
for (i=0; i<RSTRING_LEN(str); i++) {
|
|
ENUM_ELEM(ary, INT2FIX((unsigned char)RSTRING_PTR(str)[i]));
|
|
}
|
|
if (ary)
|
|
return ary;
|
|
else
|
|
return str;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.each_byte {|integer| block } -> str
|
|
* str.each_byte -> an_enumerator
|
|
*
|
|
* Passes each byte in <i>str</i> to the given block, or returns an
|
|
* enumerator if no block is given.
|
|
*
|
|
* "hello".each_byte {|c| print c, ' ' }
|
|
*
|
|
* <em>produces:</em>
|
|
*
|
|
* 104 101 108 108 111
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_each_byte(VALUE str)
|
|
{
|
|
RETURN_SIZED_ENUMERATOR(str, 0, 0, rb_str_each_byte_size);
|
|
return rb_str_enumerate_bytes(str, 0);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.bytes -> an_array
|
|
*
|
|
* Returns an array of bytes in <i>str</i>. This is a shorthand for
|
|
* <code>str.each_byte.to_a</code>.
|
|
*
|
|
* If a block is given, which is a deprecated form, works the same as
|
|
* <code>each_byte</code>.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_bytes(VALUE str)
|
|
{
|
|
VALUE ary = WANTARRAY("bytes", RSTRING_LEN(str));
|
|
return rb_str_enumerate_bytes(str, ary);
|
|
}
|
|
|
|
static VALUE
|
|
rb_str_each_char_size(VALUE str, VALUE args, VALUE eobj)
|
|
{
|
|
return rb_str_length(str);
|
|
}
|
|
|
|
static VALUE
|
|
rb_str_enumerate_chars(VALUE str, VALUE ary)
|
|
{
|
|
VALUE orig = str;
|
|
long i, len, n;
|
|
const char *ptr;
|
|
rb_encoding *enc;
|
|
|
|
str = rb_str_new_frozen(str);
|
|
ptr = RSTRING_PTR(str);
|
|
len = RSTRING_LEN(str);
|
|
enc = rb_enc_get(str);
|
|
|
|
if (ENC_CODERANGE_CLEAN_P(ENC_CODERANGE(str))) {
|
|
for (i = 0; i < len; i += n) {
|
|
n = rb_enc_fast_mbclen(ptr + i, ptr + len, enc);
|
|
ENUM_ELEM(ary, rb_str_subseq(str, i, n));
|
|
}
|
|
}
|
|
else {
|
|
for (i = 0; i < len; i += n) {
|
|
n = rb_enc_mbclen(ptr + i, ptr + len, enc);
|
|
ENUM_ELEM(ary, rb_str_subseq(str, i, n));
|
|
}
|
|
}
|
|
RB_GC_GUARD(str);
|
|
if (ary)
|
|
return ary;
|
|
else
|
|
return orig;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.each_char {|cstr| block } -> str
|
|
* str.each_char -> an_enumerator
|
|
*
|
|
* Passes each character in <i>str</i> to the given block, or returns
|
|
* an enumerator if no block is given.
|
|
*
|
|
* "hello".each_char {|c| print c, ' ' }
|
|
*
|
|
* <em>produces:</em>
|
|
*
|
|
* h e l l o
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_each_char(VALUE str)
|
|
{
|
|
RETURN_SIZED_ENUMERATOR(str, 0, 0, rb_str_each_char_size);
|
|
return rb_str_enumerate_chars(str, 0);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.chars -> an_array
|
|
*
|
|
* Returns an array of characters in <i>str</i>. This is a shorthand
|
|
* for <code>str.each_char.to_a</code>.
|
|
*
|
|
* If a block is given, which is a deprecated form, works the same as
|
|
* <code>each_char</code>.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_chars(VALUE str)
|
|
{
|
|
VALUE ary = WANTARRAY("chars", rb_str_strlen(str));
|
|
return rb_str_enumerate_chars(str, ary);
|
|
}
|
|
|
|
static VALUE
|
|
rb_str_enumerate_codepoints(VALUE str, VALUE ary)
|
|
{
|
|
VALUE orig = str;
|
|
int n;
|
|
unsigned int c;
|
|
const char *ptr, *end;
|
|
rb_encoding *enc;
|
|
|
|
if (single_byte_optimizable(str))
|
|
return rb_str_enumerate_bytes(str, ary);
|
|
|
|
str = rb_str_new_frozen(str);
|
|
ptr = RSTRING_PTR(str);
|
|
end = RSTRING_END(str);
|
|
enc = STR_ENC_GET(str);
|
|
|
|
while (ptr < end) {
|
|
c = rb_enc_codepoint_len(ptr, end, &n, enc);
|
|
ENUM_ELEM(ary, UINT2NUM(c));
|
|
ptr += n;
|
|
}
|
|
RB_GC_GUARD(str);
|
|
if (ary)
|
|
return ary;
|
|
else
|
|
return orig;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.each_codepoint {|integer| block } -> str
|
|
* str.each_codepoint -> an_enumerator
|
|
*
|
|
* Passes the Integer ordinal of each character in <i>str</i>,
|
|
* also known as a <i>codepoint</i> when applied to Unicode strings to the
|
|
* given block. For encodings other than UTF-8/UTF-16(BE|LE)/UTF-32(BE|LE),
|
|
* values are directly derived from the binary representation
|
|
* of each character.
|
|
*
|
|
* If no block is given, an enumerator is returned instead.
|
|
*
|
|
* "hello\u0639".each_codepoint {|c| print c, ' ' }
|
|
*
|
|
* <em>produces:</em>
|
|
*
|
|
* 104 101 108 108 111 1593
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_each_codepoint(VALUE str)
|
|
{
|
|
RETURN_SIZED_ENUMERATOR(str, 0, 0, rb_str_each_char_size);
|
|
return rb_str_enumerate_codepoints(str, 0);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.codepoints -> an_array
|
|
*
|
|
* Returns an array of the Integer ordinals of the
|
|
* characters in <i>str</i>. This is a shorthand for
|
|
* <code>str.each_codepoint.to_a</code>.
|
|
*
|
|
* If a block is given, which is a deprecated form, works the same as
|
|
* <code>each_codepoint</code>.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_codepoints(VALUE str)
|
|
{
|
|
VALUE ary = WANTARRAY("codepoints", rb_str_strlen(str));
|
|
return rb_str_enumerate_codepoints(str, ary);
|
|
}
|
|
|
|
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) {
|
|
#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);
|
|
#undef CASE_UTF
|
|
#undef CHARS_16BE
|
|
#undef CHARS_16LE
|
|
#undef CHARS_32BE
|
|
#undef CHARS_32LE
|
|
}
|
|
int r = onig_new(®_grapheme_cluster, source, source + source_len,
|
|
ONIG_OPTION_DEFAULT, enc, OnigDefaultSyntax, &einfo);
|
|
if (r) {
|
|
UChar message[ONIG_MAX_ERROR_MESSAGE_LEN];
|
|
onig_error_code_to_str(message, r, &einfo);
|
|
rb_fatal("cannot compile grapheme cluster regexp: %s", (char *)message);
|
|
}
|
|
if (encidx == rb_utf8_encindex()) {
|
|
reg_grapheme_cluster_utf8 = reg_grapheme_cluster;
|
|
}
|
|
}
|
|
return reg_grapheme_cluster;
|
|
}
|
|
|
|
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 = rb_enc_from_index(ENCODING_GET(str));
|
|
const char *ptr, *end;
|
|
|
|
if (!rb_enc_unicode_p(enc)) {
|
|
return rb_str_length(str);
|
|
}
|
|
|
|
reg_grapheme_cluster = get_reg_grapheme_cluster(enc);
|
|
ptr = RSTRING_PTR(str);
|
|
end = RSTRING_END(str);
|
|
|
|
while (ptr < end) {
|
|
OnigPosition len = onig_match(reg_grapheme_cluster,
|
|
(const OnigUChar *)ptr, (const OnigUChar *)end,
|
|
(const OnigUChar *)ptr, NULL, 0);
|
|
if (len <= 0) break;
|
|
grapheme_cluster_count++;
|
|
ptr += len;
|
|
}
|
|
|
|
return SIZET2NUM(grapheme_cluster_count);
|
|
}
|
|
|
|
static VALUE
|
|
rb_str_enumerate_grapheme_clusters(VALUE str, VALUE ary)
|
|
{
|
|
VALUE orig = str;
|
|
regex_t *reg_grapheme_cluster = NULL;
|
|
rb_encoding *enc = rb_enc_from_index(ENCODING_GET(str));
|
|
const char *ptr0, *ptr, *end;
|
|
|
|
if (!rb_enc_unicode_p(enc)) {
|
|
return rb_str_enumerate_chars(str, ary);
|
|
}
|
|
|
|
if (!ary) str = rb_str_new_frozen(str);
|
|
reg_grapheme_cluster = get_reg_grapheme_cluster(enc);
|
|
ptr0 = ptr = RSTRING_PTR(str);
|
|
end = RSTRING_END(str);
|
|
|
|
while (ptr < end) {
|
|
OnigPosition len = onig_match(reg_grapheme_cluster,
|
|
(const OnigUChar *)ptr, (const OnigUChar *)end,
|
|
(const OnigUChar *)ptr, NULL, 0);
|
|
if (len <= 0) break;
|
|
ENUM_ELEM(ary, rb_str_subseq(str, ptr-ptr0, len));
|
|
ptr += len;
|
|
}
|
|
RB_GC_GUARD(str);
|
|
if (ary)
|
|
return ary;
|
|
else
|
|
return orig;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.each_grapheme_cluster {|cstr| block } -> str
|
|
* str.each_grapheme_cluster -> an_enumerator
|
|
*
|
|
* Passes each grapheme cluster in <i>str</i> to the given block, or returns
|
|
* an enumerator if no block is given.
|
|
* Unlike String#each_char, this enumerates by grapheme clusters defined by
|
|
* Unicode Standard Annex #29 http://unicode.org/reports/tr29/
|
|
*
|
|
* "a\u0300".each_char.to_a.size #=> 2
|
|
* "a\u0300".each_grapheme_cluster.to_a.size #=> 1
|
|
*
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_each_grapheme_cluster(VALUE str)
|
|
{
|
|
RETURN_SIZED_ENUMERATOR(str, 0, 0, rb_str_each_grapheme_cluster_size);
|
|
return rb_str_enumerate_grapheme_clusters(str, 0);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.grapheme_clusters -> an_array
|
|
*
|
|
* Returns an array of grapheme clusters in <i>str</i>. This is a shorthand
|
|
* for <code>str.each_grapheme_cluster.to_a</code>.
|
|
*
|
|
* If a block is given, which is a deprecated form, works the same as
|
|
* <code>each_grapheme_cluster</code>.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_grapheme_clusters(VALUE str)
|
|
{
|
|
VALUE ary = WANTARRAY("grapheme_clusters", rb_str_strlen(str));
|
|
return rb_str_enumerate_grapheme_clusters(str, ary);
|
|
}
|
|
|
|
static long
|
|
chopped_length(VALUE str)
|
|
{
|
|
rb_encoding *enc = STR_ENC_GET(str);
|
|
const char *p, *p2, *beg, *end;
|
|
|
|
beg = RSTRING_PTR(str);
|
|
end = beg + RSTRING_LEN(str);
|
|
if (beg >= end) return 0;
|
|
p = rb_enc_prev_char(beg, end, end, enc);
|
|
if (!p) return 0;
|
|
if (p > beg && rb_enc_ascget(p, end, 0, enc) == '\n') {
|
|
p2 = rb_enc_prev_char(beg, p, end, enc);
|
|
if (p2 && rb_enc_ascget(p2, end, 0, enc) == '\r') p = p2;
|
|
}
|
|
return p - beg;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.chop! -> str or nil
|
|
*
|
|
* Processes <i>str</i> as for String#chop, returning <i>str</i>, or
|
|
* <code>nil</code> if <i>str</i> is the empty string. See also
|
|
* String#chomp!.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_chop_bang(VALUE str)
|
|
{
|
|
str_modify_keep_cr(str);
|
|
if (RSTRING_LEN(str) > 0) {
|
|
long len;
|
|
len = chopped_length(str);
|
|
STR_SET_LEN(str, len);
|
|
TERM_FILL(&RSTRING_PTR(str)[len], TERM_LEN(str));
|
|
if (ENC_CODERANGE(str) != ENC_CODERANGE_7BIT) {
|
|
ENC_CODERANGE_CLEAR(str);
|
|
}
|
|
return str;
|
|
}
|
|
return Qnil;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.chop -> new_str
|
|
*
|
|
* Returns a new String with the last character removed. If the
|
|
* string ends with <code>\r\n</code>, both characters are
|
|
* removed. Applying <code>chop</code> to an empty string returns an
|
|
* empty string. String#chomp is often a safer alternative, as it
|
|
* leaves the string unchanged if it doesn't end in a record
|
|
* separator.
|
|
*
|
|
* "string\r\n".chop #=> "string"
|
|
* "string\n\r".chop #=> "string\n"
|
|
* "string\n".chop #=> "string"
|
|
* "string".chop #=> "strin"
|
|
* "x".chop.chop #=> ""
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_chop(VALUE str)
|
|
{
|
|
return rb_str_subseq(str, 0, chopped_length(str));
|
|
}
|
|
|
|
static long
|
|
smart_chomp(VALUE str, const char *e, const char *p)
|
|
{
|
|
rb_encoding *enc = rb_enc_get(str);
|
|
if (rb_enc_mbminlen(enc) > 1) {
|
|
const char *pp = rb_enc_left_char_head(p, e-rb_enc_mbminlen(enc), e, enc);
|
|
if (rb_enc_is_newline(pp, e, enc)) {
|
|
e = pp;
|
|
}
|
|
pp = e - rb_enc_mbminlen(enc);
|
|
if (pp >= p) {
|
|
pp = rb_enc_left_char_head(p, pp, e, enc);
|
|
if (rb_enc_ascget(pp, e, 0, enc) == '\r') {
|
|
e = pp;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
switch (*(e-1)) { /* not e[-1] to get rid of VC bug */
|
|
case '\n':
|
|
if (--e > p && *(e-1) == '\r') {
|
|
--e;
|
|
}
|
|
break;
|
|
case '\r':
|
|
--e;
|
|
break;
|
|
}
|
|
}
|
|
return e - p;
|
|
}
|
|
|
|
static long
|
|
chompped_length(VALUE str, VALUE rs)
|
|
{
|
|
rb_encoding *enc;
|
|
int newline;
|
|
char *pp, *e, *rsptr;
|
|
long rslen;
|
|
char *const p = RSTRING_PTR(str);
|
|
long len = RSTRING_LEN(str);
|
|
|
|
if (len == 0) return 0;
|
|
e = p + len;
|
|
if (rs == rb_default_rs) {
|
|
return smart_chomp(str, e, p);
|
|
}
|
|
|
|
enc = rb_enc_get(str);
|
|
RSTRING_GETMEM(rs, rsptr, rslen);
|
|
if (rslen == 0) {
|
|
if (rb_enc_mbminlen(enc) > 1) {
|
|
while (e > p) {
|
|
pp = rb_enc_left_char_head(p, e-rb_enc_mbminlen(enc), e, enc);
|
|
if (!rb_enc_is_newline(pp, e, enc)) break;
|
|
e = pp;
|
|
pp -= rb_enc_mbminlen(enc);
|
|
if (pp >= p) {
|
|
pp = rb_enc_left_char_head(p, pp, e, enc);
|
|
if (rb_enc_ascget(pp, e, 0, enc) == '\r') {
|
|
e = pp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
while (e > p && *(e-1) == '\n') {
|
|
--e;
|
|
if (e > p && *(e-1) == '\r')
|
|
--e;
|
|
}
|
|
}
|
|
return e - p;
|
|
}
|
|
if (rslen > len) return len;
|
|
|
|
enc = rb_enc_get(rs);
|
|
newline = rsptr[rslen-1];
|
|
if (rslen == rb_enc_mbminlen(enc)) {
|
|
if (rslen == 1) {
|
|
if (newline == '\n')
|
|
return smart_chomp(str, e, p);
|
|
}
|
|
else {
|
|
if (rb_enc_is_newline(rsptr, rsptr+rslen, enc))
|
|
return smart_chomp(str, e, p);
|
|
}
|
|
}
|
|
|
|
enc = rb_enc_check(str, rs);
|
|
if (is_broken_string(rs)) {
|
|
return len;
|
|
}
|
|
pp = e - rslen;
|
|
if (p[len-1] == newline &&
|
|
(rslen <= 1 ||
|
|
memcmp(rsptr, pp, rslen) == 0)) {
|
|
if (rb_enc_left_char_head(p, pp, e, enc) == pp)
|
|
return len - rslen;
|
|
RB_GC_GUARD(rs);
|
|
}
|
|
return len;
|
|
}
|
|
|
|
/*!
|
|
* Returns the separator for arguments of rb_str_chomp.
|
|
*
|
|
* @return returns rb_ps ($/) as default, the default value of rb_ps ($/) is "\n".
|
|
*/
|
|
static VALUE
|
|
chomp_rs(int argc, const VALUE *argv)
|
|
{
|
|
rb_check_arity(argc, 0, 1);
|
|
if (argc > 0) {
|
|
VALUE rs = argv[0];
|
|
if (!NIL_P(rs)) StringValue(rs);
|
|
return rs;
|
|
}
|
|
else {
|
|
return rb_rs;
|
|
}
|
|
}
|
|
|
|
VALUE
|
|
rb_str_chomp_string(VALUE str, VALUE rs)
|
|
{
|
|
long olen = RSTRING_LEN(str);
|
|
long len = chompped_length(str, rs);
|
|
if (len >= olen) return Qnil;
|
|
str_modify_keep_cr(str);
|
|
STR_SET_LEN(str, len);
|
|
TERM_FILL(&RSTRING_PTR(str)[len], TERM_LEN(str));
|
|
if (ENC_CODERANGE(str) != ENC_CODERANGE_7BIT) {
|
|
ENC_CODERANGE_CLEAR(str);
|
|
}
|
|
return str;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.chomp!(separator=$/) -> str or nil
|
|
*
|
|
* Modifies <i>str</i> in place as described for String#chomp,
|
|
* returning <i>str</i>, or <code>nil</code> if no modifications were
|
|
* made.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_chomp_bang(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
VALUE rs;
|
|
str_modifiable(str);
|
|
if (RSTRING_LEN(str) == 0) return Qnil;
|
|
rs = chomp_rs(argc, argv);
|
|
if (NIL_P(rs)) return Qnil;
|
|
return rb_str_chomp_string(str, rs);
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.chomp(separator=$/) -> new_str
|
|
*
|
|
* Returns a new String with the given record separator removed
|
|
* from the end of <i>str</i> (if present). If <code>$/</code> has not been
|
|
* changed from the default Ruby record separator, then <code>chomp</code> also
|
|
* removes carriage return characters (that is, it will remove <code>\n</code>,
|
|
* <code>\r</code>, and <code>\r\n</code>). If <code>$/</code> is an empty string,
|
|
* it will remove all trailing newlines from the string.
|
|
*
|
|
* "hello".chomp #=> "hello"
|
|
* "hello\n".chomp #=> "hello"
|
|
* "hello\r\n".chomp #=> "hello"
|
|
* "hello\n\r".chomp #=> "hello\n"
|
|
* "hello\r".chomp #=> "hello"
|
|
* "hello \n there".chomp #=> "hello \n there"
|
|
* "hello".chomp("llo") #=> "he"
|
|
* "hello\r\n\r\n".chomp('') #=> "hello"
|
|
* "hello\r\n\r\r\n".chomp('') #=> "hello\r\n\r"
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_chomp(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
VALUE rs = chomp_rs(argc, argv);
|
|
if (NIL_P(rs)) return str_duplicate(rb_cString, str);
|
|
return rb_str_subseq(str, 0, chompped_length(str, rs));
|
|
}
|
|
|
|
static long
|
|
lstrip_offset(VALUE str, const char *s, const char *e, rb_encoding *enc)
|
|
{
|
|
const char *const start = s;
|
|
|
|
if (!s || s >= e) return 0;
|
|
|
|
/* remove spaces at head */
|
|
if (single_byte_optimizable(str)) {
|
|
while (s < e && (*s == '\0' || ascii_isspace(*s))) s++;
|
|
}
|
|
else {
|
|
while (s < e) {
|
|
int n;
|
|
unsigned int cc = rb_enc_codepoint_len(s, e, &n, enc);
|
|
|
|
if (cc && !rb_isspace(cc)) break;
|
|
s += n;
|
|
}
|
|
}
|
|
return s - start;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.lstrip! -> self or nil
|
|
*
|
|
* Removes leading whitespace from the receiver.
|
|
* Returns the altered receiver, or +nil+ if no change was made.
|
|
* See also String#rstrip! and String#strip!.
|
|
*
|
|
* Refer to String#strip for the definition of whitespace.
|
|
*
|
|
* " hello ".lstrip! #=> "hello "
|
|
* "hello ".lstrip! #=> nil
|
|
* "hello".lstrip! #=> nil
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_lstrip_bang(VALUE str)
|
|
{
|
|
rb_encoding *enc;
|
|
char *start, *s;
|
|
long olen, loffset;
|
|
|
|
str_modify_keep_cr(str);
|
|
enc = STR_ENC_GET(str);
|
|
RSTRING_GETMEM(str, start, olen);
|
|
loffset = lstrip_offset(str, start, start+olen, enc);
|
|
if (loffset > 0) {
|
|
long len = olen-loffset;
|
|
s = start + loffset;
|
|
memmove(start, s, len);
|
|
STR_SET_LEN(str, len);
|
|
#if !SHARABLE_MIDDLE_SUBSTRING
|
|
TERM_FILL(start+len, rb_enc_mbminlen(enc));
|
|
#endif
|
|
return str;
|
|
}
|
|
return Qnil;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.lstrip -> new_str
|
|
*
|
|
* Returns a copy of the receiver with leading whitespace removed.
|
|
* See also String#rstrip and String#strip.
|
|
*
|
|
* Refer to String#strip for the definition of whitespace.
|
|
*
|
|
* " hello ".lstrip #=> "hello "
|
|
* "hello".lstrip #=> "hello"
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_lstrip(VALUE str)
|
|
{
|
|
char *start;
|
|
long len, loffset;
|
|
RSTRING_GETMEM(str, start, len);
|
|
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);
|
|
}
|
|
|
|
static long
|
|
rstrip_offset(VALUE str, const char *s, const char *e, rb_encoding *enc)
|
|
{
|
|
const char *t;
|
|
|
|
rb_str_check_dummy_enc(enc);
|
|
if (!s || s >= e) return 0;
|
|
t = e;
|
|
|
|
/* remove trailing spaces or '\0's */
|
|
if (single_byte_optimizable(str)) {
|
|
unsigned char c;
|
|
while (s < t && ((c = *(t-1)) == '\0' || ascii_isspace(c))) t--;
|
|
}
|
|
else {
|
|
char *tp;
|
|
|
|
while ((tp = rb_enc_prev_char(s, t, e, enc)) != NULL) {
|
|
unsigned int c = rb_enc_codepoint(tp, e, enc);
|
|
if (c && !rb_isspace(c)) break;
|
|
t = tp;
|
|
}
|
|
}
|
|
return e - t;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.rstrip! -> self or nil
|
|
*
|
|
* Removes trailing whitespace from the receiver.
|
|
* Returns the altered receiver, or +nil+ if no change was made.
|
|
* See also String#lstrip! and String#strip!.
|
|
*
|
|
* Refer to String#strip for the definition of whitespace.
|
|
*
|
|
* " hello ".rstrip! #=> " hello"
|
|
* " hello".rstrip! #=> nil
|
|
* "hello".rstrip! #=> nil
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_rstrip_bang(VALUE str)
|
|
{
|
|
rb_encoding *enc;
|
|
char *start;
|
|
long olen, roffset;
|
|
|
|
str_modify_keep_cr(str);
|
|
enc = STR_ENC_GET(str);
|
|
RSTRING_GETMEM(str, start, olen);
|
|
roffset = rstrip_offset(str, start, start+olen, enc);
|
|
if (roffset > 0) {
|
|
long len = olen - roffset;
|
|
|
|
STR_SET_LEN(str, len);
|
|
#if !SHARABLE_MIDDLE_SUBSTRING
|
|
TERM_FILL(start+len, rb_enc_mbminlen(enc));
|
|
#endif
|
|
return str;
|
|
}
|
|
return Qnil;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.rstrip -> new_str
|
|
*
|
|
* Returns a copy of the receiver with trailing whitespace removed.
|
|
* See also String#lstrip and String#strip.
|
|
*
|
|
* Refer to String#strip for the definition of whitespace.
|
|
*
|
|
* " hello ".rstrip #=> " hello"
|
|
* "hello".rstrip #=> "hello"
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_rstrip(VALUE str)
|
|
{
|
|
rb_encoding *enc;
|
|
char *start;
|
|
long olen, roffset;
|
|
|
|
enc = STR_ENC_GET(str);
|
|
RSTRING_GETMEM(str, start, olen);
|
|
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);
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.strip! -> self or nil
|
|
*
|
|
* Removes leading and trailing whitespace from the receiver.
|
|
* Returns the altered receiver, or +nil+ if there was no change.
|
|
*
|
|
* Refer to String#strip for the definition of whitespace.
|
|
*
|
|
* " hello ".strip! #=> "hello"
|
|
* "hello".strip! #=> nil
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_strip_bang(VALUE str)
|
|
{
|
|
char *start;
|
|
long olen, loffset, roffset;
|
|
rb_encoding *enc;
|
|
|
|
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 (loffset > 0 || roffset > 0) {
|
|
long len = olen-roffset;
|
|
if (loffset > 0) {
|
|
len -= loffset;
|
|
memmove(start, start + loffset, len);
|
|
}
|
|
STR_SET_LEN(str, len);
|
|
#if !SHARABLE_MIDDLE_SUBSTRING
|
|
TERM_FILL(start+len, rb_enc_mbminlen(enc));
|
|
#endif
|
|
return str;
|
|
}
|
|
return Qnil;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.strip -> new_str
|
|
*
|
|
* Returns a copy of the receiver with leading and trailing whitespace removed.
|
|
*
|
|
* Whitespace is defined as any of the following characters:
|
|
* null, horizontal tab, line feed, vertical tab, form feed, carriage return, space.
|
|
*
|
|
* " hello ".strip #=> "hello"
|
|
* "\tgoodbye\r\n".strip #=> "goodbye"
|
|
* "\x00\t\n\v\f\r ".strip #=> ""
|
|
* "hello".strip #=> "hello"
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_strip(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 (loffset <= 0 && roffset <= 0) return str_duplicate(rb_cString, str);
|
|
return rb_str_subseq(str, loffset, olen-loffset-roffset);
|
|
}
|
|
|
|
static VALUE
|
|
scan_once(VALUE str, VALUE pat, long *start, int set_backref_str)
|
|
{
|
|
VALUE result, match;
|
|
struct re_registers *regs;
|
|
int i;
|
|
long end, pos = rb_pat_search(pat, str, *start, set_backref_str);
|
|
if (pos >= 0) {
|
|
if (BUILTIN_TYPE(pat) == T_STRING) {
|
|
regs = NULL;
|
|
end = pos + RSTRING_LEN(pat);
|
|
}
|
|
else {
|
|
match = rb_backref_get();
|
|
regs = RMATCH_REGS(match);
|
|
pos = BEG(0);
|
|
end = END(0);
|
|
}
|
|
if (pos == end) {
|
|
rb_encoding *enc = STR_ENC_GET(str);
|
|
/*
|
|
* Always consume at least one character of the input string
|
|
*/
|
|
if (RSTRING_LEN(str) > end)
|
|
*start = end + rb_enc_fast_mbclen(RSTRING_PTR(str) + end,
|
|
RSTRING_END(str), enc);
|
|
else
|
|
*start = end + 1;
|
|
}
|
|
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));
|
|
}
|
|
rb_ary_push(result, s);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
return Qnil;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.scan(pattern) -> array
|
|
* str.scan(pattern) {|match, ...| block } -> str
|
|
*
|
|
* Both forms iterate through <i>str</i>, matching the pattern (which may be a
|
|
* Regexp or a String). For each match, a result is
|
|
* generated and either added to the result array or passed to the block. If
|
|
* the pattern contains no groups, each individual result consists of the
|
|
* matched string, <code>$&</code>. If the pattern contains groups, each
|
|
* individual result is itself an array containing one entry per group.
|
|
*
|
|
* a = "cruel world"
|
|
* a.scan(/\w+/) #=> ["cruel", "world"]
|
|
* a.scan(/.../) #=> ["cru", "el ", "wor"]
|
|
* a.scan(/(...)/) #=> [["cru"], ["el "], ["wor"]]
|
|
* a.scan(/(..)(..)/) #=> [["cr", "ue"], ["l ", "wo"]]
|
|
*
|
|
* And the block form:
|
|
*
|
|
* a.scan(/\w+/) {|w| print "<<#{w}>> " }
|
|
* print "\n"
|
|
* a.scan(/(.)(.)/) {|x,y| print y, x }
|
|
* print "\n"
|
|
*
|
|
* <em>produces:</em>
|
|
*
|
|
* <<cruel>> <<world>>
|
|
* rceu lowlr
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_scan(VALUE str, VALUE pat)
|
|
{
|
|
VALUE result;
|
|
long start = 0;
|
|
long last = -1, prev = 0;
|
|
char *p = RSTRING_PTR(str); long len = RSTRING_LEN(str);
|
|
|
|
pat = get_pat_quoted(pat, 1);
|
|
mustnot_broken(str);
|
|
if (!rb_block_given_p()) {
|
|
VALUE ary = rb_ary_new();
|
|
|
|
while (!NIL_P(result = scan_once(str, pat, &start, 0))) {
|
|
last = prev;
|
|
prev = start;
|
|
rb_ary_push(ary, result);
|
|
}
|
|
if (last >= 0) rb_pat_search(pat, str, last, 1);
|
|
else rb_backref_set(Qnil);
|
|
return ary;
|
|
}
|
|
|
|
while (!NIL_P(result = scan_once(str, pat, &start, 1))) {
|
|
last = prev;
|
|
prev = start;
|
|
rb_yield(result);
|
|
str_mod_check(str, p, len);
|
|
}
|
|
if (last >= 0) rb_pat_search(pat, str, last, 1);
|
|
return str;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.hex -> integer
|
|
*
|
|
* Treats leading characters from <i>str</i> as a string of hexadecimal digits
|
|
* (with an optional sign and an optional <code>0x</code>) and returns the
|
|
* corresponding number. Zero is returned on error.
|
|
*
|
|
* "0x0a".hex #=> 10
|
|
* "-1234".hex #=> -4660
|
|
* "0".hex #=> 0
|
|
* "wombat".hex #=> 0
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_hex(VALUE str)
|
|
{
|
|
return rb_str_to_inum(str, 16, FALSE);
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.oct -> integer
|
|
*
|
|
* Treats leading characters of <i>str</i> as a string of octal digits (with an
|
|
* optional sign) and returns the corresponding number. Returns 0 if the
|
|
* conversion fails.
|
|
*
|
|
* "123".oct #=> 83
|
|
* "-377".oct #=> -255
|
|
* "bad".oct #=> 0
|
|
* "0377bad".oct #=> 255
|
|
*
|
|
* If +str+ starts with <code>0</code>, radix indicators are honored.
|
|
* See Kernel#Integer.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_oct(VALUE str)
|
|
{
|
|
return rb_str_to_inum(str, -8, FALSE);
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.crypt(salt_str) -> new_str
|
|
*
|
|
* Returns the string generated by calling <code>crypt(3)</code>
|
|
* standard library function with <code>str</code> and
|
|
* <code>salt_str</code>, in this order, as its arguments. Please do
|
|
* not use this method any longer. It is legacy; provided only for
|
|
* backward compatibility with ruby scripts in earlier days. It is
|
|
* bad to use in contemporary programs for several reasons:
|
|
*
|
|
* * Behaviour of C's <code>crypt(3)</code> depends on the OS it is
|
|
* run. The generated string lacks data portability.
|
|
*
|
|
* * On some OSes such as Mac OS, <code>crypt(3)</code> never fails
|
|
* (i.e. silently ends up in unexpected results).
|
|
*
|
|
* * On some OSes such as Mac OS, <code>crypt(3)</code> is not
|
|
* thread safe.
|
|
*
|
|
* * So-called "traditional" usage of <code>crypt(3)</code> is very
|
|
* very very weak. According to its manpage, Linux's traditional
|
|
* <code>crypt(3)</code> output has only 2**56 variations; too
|
|
* easy to brute force today. And this is the default behaviour.
|
|
*
|
|
* * In order to make things robust some OSes implement so-called
|
|
* "modular" usage. To go through, you have to do a complex
|
|
* build-up of the <code>salt_str</code> parameter, by hand.
|
|
* Failure in generation of a proper salt string tends not to
|
|
* yield any errors; typos in parameters are normally not
|
|
* detectable.
|
|
*
|
|
* * For instance, in the following example, the second invocation
|
|
* of String#crypt is wrong; it has a typo in "round=" (lacks
|
|
* "s"). However the call does not fail and something unexpected
|
|
* is generated.
|
|
*
|
|
* "foo".crypt("$5$rounds=1000$salt$") # OK, proper usage
|
|
* "foo".crypt("$5$round=1000$salt$") # Typo not detected
|
|
*
|
|
* * Even in the "modular" mode, some hash functions are considered
|
|
* archaic and no longer recommended at all; for instance module
|
|
* <code>$1$</code> is officially abandoned by its author: see
|
|
* http://phk.freebsd.dk/sagas/md5crypt_eol/ . For another
|
|
* instance module <code>$3$</code> is considered completely
|
|
* broken: see the manpage of FreeBSD.
|
|
*
|
|
* * On some OS such as Mac OS, there is no modular mode. Yet, as
|
|
* written above, <code>crypt(3)</code> on Mac OS never fails.
|
|
* This means even if you build up a proper salt string it
|
|
* generates a traditional DES hash anyways, and there is no way
|
|
* for you to be aware of.
|
|
*
|
|
* "foo".crypt("$5$rounds=1000$salt$") # => "$5fNPQMxC5j6."
|
|
*
|
|
* If for some reason you cannot migrate to other secure contemporary
|
|
* password hashing algorithms, install the string-crypt gem and
|
|
* <code>require 'string/crypt'</code> to continue using it.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_crypt(VALUE str, VALUE salt)
|
|
{
|
|
#ifdef HAVE_CRYPT_R
|
|
VALUE databuf;
|
|
struct crypt_data *data;
|
|
# define CRYPT_END() ALLOCV_END(databuf)
|
|
#else
|
|
extern char *crypt(const char *, const char *);
|
|
# define CRYPT_END() (void)0
|
|
#endif
|
|
VALUE result;
|
|
const char *s, *saltp;
|
|
char *res;
|
|
#ifdef BROKEN_CRYPT
|
|
char salt_8bit_clean[3];
|
|
#endif
|
|
|
|
StringValue(salt);
|
|
mustnot_wchar(str);
|
|
mustnot_wchar(salt);
|
|
s = StringValueCStr(str);
|
|
saltp = RSTRING_PTR(salt);
|
|
if (RSTRING_LEN(salt) < 2 || !saltp[0] || !saltp[1]) {
|
|
rb_raise(rb_eArgError, "salt too short (need >=2 bytes)");
|
|
}
|
|
|
|
#ifdef BROKEN_CRYPT
|
|
if (!ISASCII((unsigned char)saltp[0]) || !ISASCII((unsigned char)saltp[1])) {
|
|
salt_8bit_clean[0] = saltp[0] & 0x7f;
|
|
salt_8bit_clean[1] = saltp[1] & 0x7f;
|
|
salt_8bit_clean[2] = '\0';
|
|
saltp = salt_8bit_clean;
|
|
}
|
|
#endif
|
|
#ifdef HAVE_CRYPT_R
|
|
data = ALLOCV(databuf, sizeof(struct crypt_data));
|
|
# ifdef HAVE_STRUCT_CRYPT_DATA_INITIALIZED
|
|
data->initialized = 0;
|
|
# endif
|
|
res = crypt_r(s, saltp, data);
|
|
#else
|
|
res = crypt(s, saltp);
|
|
#endif
|
|
if (!res) {
|
|
int err = errno;
|
|
CRYPT_END();
|
|
rb_syserr_fail(err, "crypt");
|
|
}
|
|
result = rb_str_new_cstr(res);
|
|
CRYPT_END();
|
|
return result;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.ord -> integer
|
|
*
|
|
* Returns the Integer ordinal of a one-character string.
|
|
*
|
|
* "a".ord #=> 97
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_ord(VALUE s)
|
|
{
|
|
unsigned int c;
|
|
|
|
c = rb_enc_codepoint(RSTRING_PTR(s), RSTRING_END(s), STR_ENC_GET(s));
|
|
return UINT2NUM(c);
|
|
}
|
|
/*
|
|
* call-seq:
|
|
* str.sum(n=16) -> integer
|
|
*
|
|
* Returns a basic <em>n</em>-bit checksum of the characters in <i>str</i>,
|
|
* where <em>n</em> is the optional Integer parameter, defaulting
|
|
* to 16. The result is simply the sum of the binary value of each byte in
|
|
* <i>str</i> modulo <code>2**n - 1</code>. This is not a particularly good
|
|
* checksum.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_sum(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
int bits = 16;
|
|
char *ptr, *p, *pend;
|
|
long len;
|
|
VALUE sum = INT2FIX(0);
|
|
unsigned long sum0 = 0;
|
|
|
|
if (rb_check_arity(argc, 0, 1) && (bits = NUM2INT(argv[0])) < 0) {
|
|
bits = 0;
|
|
}
|
|
ptr = p = RSTRING_PTR(str);
|
|
len = RSTRING_LEN(str);
|
|
pend = p + len;
|
|
|
|
while (p < pend) {
|
|
if (FIXNUM_MAX - UCHAR_MAX < sum0) {
|
|
sum = rb_funcall(sum, '+', 1, LONG2FIX(sum0));
|
|
str_mod_check(str, ptr, len);
|
|
sum0 = 0;
|
|
}
|
|
sum0 += (unsigned char)*p;
|
|
p++;
|
|
}
|
|
|
|
if (bits == 0) {
|
|
if (sum0) {
|
|
sum = rb_funcall(sum, '+', 1, LONG2FIX(sum0));
|
|
}
|
|
}
|
|
else {
|
|
if (sum == INT2FIX(0)) {
|
|
if (bits < (int)sizeof(long)*CHAR_BIT) {
|
|
sum0 &= (((unsigned long)1)<<bits)-1;
|
|
}
|
|
sum = LONG2FIX(sum0);
|
|
}
|
|
else {
|
|
VALUE mod;
|
|
|
|
if (sum0) {
|
|
sum = rb_funcall(sum, '+', 1, LONG2FIX(sum0));
|
|
}
|
|
|
|
mod = rb_funcall(INT2FIX(1), idLTLT, 1, INT2FIX(bits));
|
|
mod = rb_funcall(mod, '-', 1, INT2FIX(1));
|
|
sum = rb_funcall(sum, '&', 1, mod);
|
|
}
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
static VALUE
|
|
rb_str_justify(int argc, VALUE *argv, VALUE str, char jflag)
|
|
{
|
|
rb_encoding *enc;
|
|
VALUE w;
|
|
long width, len, flen = 1, fclen = 1;
|
|
VALUE res;
|
|
char *p;
|
|
const char *f = " ";
|
|
long n, size, llen, rlen, llen2 = 0, rlen2 = 0;
|
|
VALUE pad;
|
|
int singlebyte = 1, cr;
|
|
int termlen;
|
|
|
|
rb_scan_args(argc, argv, "11", &w, &pad);
|
|
enc = STR_ENC_GET(str);
|
|
termlen = rb_enc_mbminlen(enc);
|
|
width = NUM2LONG(w);
|
|
if (argc == 2) {
|
|
StringValue(pad);
|
|
enc = rb_enc_check(str, pad);
|
|
f = RSTRING_PTR(pad);
|
|
flen = RSTRING_LEN(pad);
|
|
fclen = str_strlen(pad, enc); /* rb_enc_check */
|
|
singlebyte = single_byte_optimizable(pad);
|
|
if (flen == 0 || fclen == 0) {
|
|
rb_raise(rb_eArgError, "zero width padding");
|
|
}
|
|
}
|
|
len = str_strlen(str, enc); /* rb_enc_check */
|
|
if (width < 0 || len >= width) return str_duplicate(rb_cString, str);
|
|
n = width - len;
|
|
llen = (jflag == 'l') ? 0 : ((jflag == 'r') ? n : n/2);
|
|
rlen = n - llen;
|
|
cr = ENC_CODERANGE(str);
|
|
if (flen > 1) {
|
|
llen2 = str_offset(f, f + flen, llen % fclen, enc, singlebyte);
|
|
rlen2 = str_offset(f, f + flen, rlen % fclen, enc, singlebyte);
|
|
}
|
|
size = RSTRING_LEN(str);
|
|
if ((len = llen / fclen + rlen / fclen) >= LONG_MAX / flen ||
|
|
(len *= flen) >= LONG_MAX - llen2 - rlen2 ||
|
|
(len += llen2 + rlen2) >= LONG_MAX - size) {
|
|
rb_raise(rb_eArgError, "argument too big");
|
|
}
|
|
len += size;
|
|
res = str_new0(rb_cString, 0, len, termlen);
|
|
p = RSTRING_PTR(res);
|
|
if (flen <= 1) {
|
|
memset(p, *f, llen);
|
|
p += llen;
|
|
}
|
|
else {
|
|
while (llen >= fclen) {
|
|
memcpy(p,f,flen);
|
|
p += flen;
|
|
llen -= fclen;
|
|
}
|
|
if (llen > 0) {
|
|
memcpy(p, f, llen2);
|
|
p += llen2;
|
|
}
|
|
}
|
|
memcpy(p, RSTRING_PTR(str), size);
|
|
p += size;
|
|
if (flen <= 1) {
|
|
memset(p, *f, rlen);
|
|
p += rlen;
|
|
}
|
|
else {
|
|
while (rlen >= fclen) {
|
|
memcpy(p,f,flen);
|
|
p += flen;
|
|
rlen -= fclen;
|
|
}
|
|
if (rlen > 0) {
|
|
memcpy(p, f, rlen2);
|
|
p += rlen2;
|
|
}
|
|
}
|
|
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)
|
|
ENC_CODERANGE_SET(res, cr);
|
|
|
|
RB_GC_GUARD(pad);
|
|
return res;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.ljust(integer, padstr=' ') -> new_str
|
|
*
|
|
* If <i>integer</i> is greater than the length of <i>str</i>, returns a new
|
|
* String of length <i>integer</i> with <i>str</i> left justified
|
|
* and padded with <i>padstr</i>; otherwise, returns <i>str</i>.
|
|
*
|
|
* "hello".ljust(4) #=> "hello"
|
|
* "hello".ljust(20) #=> "hello "
|
|
* "hello".ljust(20, '1234') #=> "hello123412341234123"
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_ljust(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
return rb_str_justify(argc, argv, str, 'l');
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.rjust(integer, padstr=' ') -> new_str
|
|
*
|
|
* If <i>integer</i> is greater than the length of <i>str</i>, returns a new
|
|
* String of length <i>integer</i> with <i>str</i> right justified
|
|
* and padded with <i>padstr</i>; otherwise, returns <i>str</i>.
|
|
*
|
|
* "hello".rjust(4) #=> "hello"
|
|
* "hello".rjust(20) #=> " hello"
|
|
* "hello".rjust(20, '1234') #=> "123412341234123hello"
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_rjust(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
return rb_str_justify(argc, argv, str, 'r');
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.center(width, padstr=' ') -> new_str
|
|
*
|
|
* Centers +str+ in +width+. If +width+ is greater than the length of +str+,
|
|
* returns a new String of length +width+ with +str+ centered and padded with
|
|
* +padstr+; otherwise, returns +str+.
|
|
*
|
|
* "hello".center(4) #=> "hello"
|
|
* "hello".center(20) #=> " hello "
|
|
* "hello".center(20, '123') #=> "1231231hello12312312"
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_center(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
return rb_str_justify(argc, argv, str, 'c');
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.partition(sep) -> [head, sep, tail]
|
|
* str.partition(regexp) -> [head, match, tail]
|
|
*
|
|
* Searches <i>sep</i> or pattern (<i>regexp</i>) in the string
|
|
* and returns the part before it, the match, and the part
|
|
* after it.
|
|
* If it is not found, returns two empty strings and <i>str</i>.
|
|
*
|
|
* "hello".partition("l") #=> ["he", "l", "lo"]
|
|
* "hello".partition("x") #=> ["hello", "", ""]
|
|
* "hello".partition(/.l/) #=> ["h", "el", "lo"]
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_partition(VALUE str, VALUE sep)
|
|
{
|
|
long pos;
|
|
|
|
sep = get_pat_quoted(sep, 0);
|
|
if (RB_TYPE_P(sep, T_REGEXP)) {
|
|
if (rb_reg_search(sep, str, 0, 0) < 0) {
|
|
goto failed;
|
|
}
|
|
VALUE match = rb_backref_get();
|
|
struct re_registers *regs = RMATCH_REGS(match);
|
|
|
|
pos = BEG(0);
|
|
sep = rb_str_subseq(str, pos, END(0) - pos);
|
|
}
|
|
else {
|
|
pos = rb_str_index(str, sep, 0);
|
|
if (pos < 0) goto failed;
|
|
}
|
|
return rb_ary_new3(3, rb_str_subseq(str, 0, pos),
|
|
sep,
|
|
rb_str_subseq(str, pos+RSTRING_LEN(sep),
|
|
RSTRING_LEN(str)-pos-RSTRING_LEN(sep)));
|
|
|
|
failed:
|
|
return rb_ary_new3(3, str_duplicate(rb_cString, str), str_new_empty_String(str), str_new_empty_String(str));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.rpartition(sep) -> [head, sep, tail]
|
|
* str.rpartition(regexp) -> [head, match, tail]
|
|
*
|
|
* Searches <i>sep</i> or pattern (<i>regexp</i>) in the string from the end
|
|
* of the string, and returns the part before it, the match, and the part
|
|
* after it.
|
|
* If it is not found, returns two empty strings and <i>str</i>.
|
|
*
|
|
* "hello".rpartition("l") #=> ["hel", "l", "o"]
|
|
* "hello".rpartition("x") #=> ["", "", "hello"]
|
|
* "hello".rpartition(/.l/) #=> ["he", "ll", "o"]
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_rpartition(VALUE str, VALUE sep)
|
|
{
|
|
long pos = RSTRING_LEN(str);
|
|
|
|
sep = get_pat_quoted(sep, 0);
|
|
if (RB_TYPE_P(sep, T_REGEXP)) {
|
|
if (rb_reg_search(sep, str, pos, 1) < 0) {
|
|
goto failed;
|
|
}
|
|
VALUE match = rb_backref_get();
|
|
struct re_registers *regs = RMATCH_REGS(match);
|
|
|
|
pos = BEG(0);
|
|
sep = rb_str_subseq(str, pos, END(0) - pos);
|
|
}
|
|
else {
|
|
pos = rb_str_sublen(str, pos);
|
|
pos = rb_str_rindex(str, sep, pos);
|
|
if(pos < 0) {
|
|
goto failed;
|
|
}
|
|
pos = rb_str_offset(str, pos);
|
|
}
|
|
|
|
return rb_ary_new3(3, rb_str_subseq(str, 0, pos),
|
|
sep,
|
|
rb_str_subseq(str, pos+RSTRING_LEN(sep),
|
|
RSTRING_LEN(str)-pos-RSTRING_LEN(sep)));
|
|
failed:
|
|
return rb_ary_new3(3, str_new_empty_String(str), str_new_empty_String(str), str_duplicate(rb_cString, str));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.start_with?([prefixes]+) -> true or false
|
|
*
|
|
* Returns true if +str+ starts with one of the +prefixes+ given.
|
|
* Each of the +prefixes+ should be a String or a Regexp.
|
|
*
|
|
* "hello".start_with?("hell") #=> true
|
|
* "hello".start_with?(/H/i) #=> true
|
|
*
|
|
* # returns true if one of the prefixes matches.
|
|
* "hello".start_with?("heaven", "hell") #=> true
|
|
* "hello".start_with?("heaven", "paradise") #=> false
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_start_with(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
int i;
|
|
|
|
for (i=0; i<argc; i++) {
|
|
VALUE tmp = argv[i];
|
|
if (RB_TYPE_P(tmp, T_REGEXP)) {
|
|
if (rb_reg_start_with_p(tmp, str))
|
|
return Qtrue;
|
|
}
|
|
else {
|
|
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)
|
|
return Qtrue;
|
|
}
|
|
}
|
|
return Qfalse;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.end_with?([suffixes]+) -> true or false
|
|
*
|
|
* Returns true if +str+ ends with one of the +suffixes+ given.
|
|
*
|
|
* "hello".end_with?("ello") #=> true
|
|
*
|
|
* # returns true if one of the +suffixes+ matches.
|
|
* "hello".end_with?("heaven", "ello") #=> true
|
|
* "hello".end_with?("heaven", "paradise") #=> false
|
|
*/
|
|
|
|
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];
|
|
StringValue(tmp);
|
|
enc = rb_enc_check(str, tmp);
|
|
if (RSTRING_LEN(str) < RSTRING_LEN(tmp)) continue;
|
|
p = RSTRING_PTR(str);
|
|
e = p + RSTRING_LEN(str);
|
|
s = e - RSTRING_LEN(tmp);
|
|
if (rb_enc_left_char_head(p, s, e, enc) != s)
|
|
continue;
|
|
if (memcmp(s, RSTRING_PTR(tmp), RSTRING_LEN(tmp)) == 0)
|
|
return Qtrue;
|
|
}
|
|
return Qfalse;
|
|
}
|
|
|
|
/*!
|
|
* Returns the length of the <i>prefix</i> to be deleted in the given <i>str</i>,
|
|
* returning 0 if <i>str</i> does not start with the <i>prefix</i>.
|
|
*
|
|
* @param str the target
|
|
* @param prefix the prefix
|
|
* @retval 0 if the given <i>str</i> does not start with the given <i>prefix</i>
|
|
* @retval Positive-Integer otherwise
|
|
*/
|
|
static long
|
|
deleted_prefix_length(VALUE str, VALUE prefix)
|
|
{
|
|
char *strptr, *prefixptr;
|
|
long olen, prefixlen;
|
|
|
|
StringValue(prefix);
|
|
if (is_broken_string(prefix)) return 0;
|
|
rb_enc_check(str, prefix);
|
|
|
|
/* return 0 if not start with prefix */
|
|
prefixlen = RSTRING_LEN(prefix);
|
|
if (prefixlen <= 0) return 0;
|
|
olen = RSTRING_LEN(str);
|
|
if (olen < prefixlen) return 0;
|
|
strptr = RSTRING_PTR(str);
|
|
prefixptr = RSTRING_PTR(prefix);
|
|
if (memcmp(strptr, prefixptr, prefixlen) != 0) return 0;
|
|
|
|
return prefixlen;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.delete_prefix!(prefix) -> self or nil
|
|
*
|
|
* Deletes leading <code>prefix</code> from <i>str</i>, returning
|
|
* <code>nil</code> if no change was made.
|
|
*
|
|
* "hello".delete_prefix!("hel") #=> "lo"
|
|
* "hello".delete_prefix!("llo") #=> nil
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_delete_prefix_bang(VALUE str, VALUE prefix)
|
|
{
|
|
long prefixlen;
|
|
str_modify_keep_cr(str);
|
|
|
|
prefixlen = deleted_prefix_length(str, prefix);
|
|
if (prefixlen <= 0) return Qnil;
|
|
|
|
return rb_str_drop_bytes(str, prefixlen);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.delete_prefix(prefix) -> new_str
|
|
*
|
|
* Returns a copy of <i>str</i> with leading <code>prefix</code> deleted.
|
|
*
|
|
* "hello".delete_prefix("hel") #=> "lo"
|
|
* "hello".delete_prefix("llo") #=> "hello"
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_delete_prefix(VALUE str, VALUE prefix)
|
|
{
|
|
long prefixlen;
|
|
|
|
prefixlen = deleted_prefix_length(str, prefix);
|
|
if (prefixlen <= 0) return str_duplicate(rb_cString, str);
|
|
|
|
return rb_str_subseq(str, prefixlen, RSTRING_LEN(str) - prefixlen);
|
|
}
|
|
|
|
/*!
|
|
* Returns the length of the <i>suffix</i> to be deleted in the given <i>str</i>,
|
|
* returning 0 if <i>str</i> does not end with the <i>suffix</i>.
|
|
*
|
|
* @param str the target
|
|
* @param suffix the suffix
|
|
* @retval 0 if the given <i>str</i> does not end with the given <i>suffix</i>
|
|
* @retval Positive-Integer otherwise
|
|
*/
|
|
static long
|
|
deleted_suffix_length(VALUE str, VALUE suffix)
|
|
{
|
|
char *strptr, *suffixptr, *s;
|
|
long olen, suffixlen;
|
|
rb_encoding *enc;
|
|
|
|
StringValue(suffix);
|
|
if (is_broken_string(suffix)) return 0;
|
|
enc = rb_enc_check(str, suffix);
|
|
|
|
/* return 0 if not start with suffix */
|
|
suffixlen = RSTRING_LEN(suffix);
|
|
if (suffixlen <= 0) return 0;
|
|
olen = RSTRING_LEN(str);
|
|
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;
|
|
|
|
return suffixlen;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.delete_suffix!(suffix) -> self or nil
|
|
*
|
|
* Deletes trailing <code>suffix</code> from <i>str</i>, returning
|
|
* <code>nil</code> if no change was made.
|
|
*
|
|
* "hello".delete_suffix!("llo") #=> "he"
|
|
* "hello".delete_suffix!("hel") #=> nil
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_delete_suffix_bang(VALUE str, VALUE suffix)
|
|
{
|
|
long olen, suffixlen, len;
|
|
str_modifiable(str);
|
|
|
|
suffixlen = deleted_suffix_length(str, suffix);
|
|
if (suffixlen <= 0) return Qnil;
|
|
|
|
olen = RSTRING_LEN(str);
|
|
str_modify_keep_cr(str);
|
|
len = olen - suffixlen;
|
|
STR_SET_LEN(str, len);
|
|
TERM_FILL(&RSTRING_PTR(str)[len], TERM_LEN(str));
|
|
if (ENC_CODERANGE(str) != ENC_CODERANGE_7BIT) {
|
|
ENC_CODERANGE_CLEAR(str);
|
|
}
|
|
return str;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.delete_suffix(suffix) -> new_str
|
|
*
|
|
* Returns a copy of <i>str</i> with trailing <code>suffix</code> deleted.
|
|
*
|
|
* "hello".delete_suffix("llo") #=> "he"
|
|
* "hello".delete_suffix("hel") #=> "hello"
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_delete_suffix(VALUE str, VALUE suffix)
|
|
{
|
|
long suffixlen;
|
|
|
|
suffixlen = deleted_suffix_length(str, suffix);
|
|
if (suffixlen <= 0) return str_duplicate(rb_cString, str);
|
|
|
|
return rb_str_subseq(str, 0, RSTRING_LEN(str) - suffixlen);
|
|
}
|
|
|
|
void
|
|
rb_str_setter(VALUE val, ID id, VALUE *var)
|
|
{
|
|
if (!NIL_P(val) && !RB_TYPE_P(val, T_STRING)) {
|
|
rb_raise(rb_eTypeError, "value of %"PRIsVALUE" must be String", rb_id2str(id));
|
|
}
|
|
*var = val;
|
|
}
|
|
|
|
static void
|
|
rb_fs_setter(VALUE val, ID id, VALUE *var)
|
|
{
|
|
val = rb_fs_check(val);
|
|
if (!val) {
|
|
rb_raise(rb_eTypeError,
|
|
"value of %"PRIsVALUE" must be String or Regexp",
|
|
rb_id2str(id));
|
|
}
|
|
if (!NIL_P(val)) {
|
|
rb_warn_deprecated("`$;'", NULL);
|
|
}
|
|
*var = val;
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.force_encoding(encoding) -> str
|
|
*
|
|
* Changes the encoding to +encoding+ and returns self.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_force_encoding(VALUE str, VALUE enc)
|
|
{
|
|
str_modifiable(str);
|
|
rb_enc_associate(str, rb_to_encoding(enc));
|
|
ENC_CODERANGE_CLEAR(str);
|
|
return str;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.b -> str
|
|
*
|
|
* Returns a copied string whose encoding is ASCII-8BIT.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_b(VALUE str)
|
|
{
|
|
VALUE str2 = str_alloc(rb_cString);
|
|
str_replace_shared_without_enc(str2, str);
|
|
ENC_CODERANGE_CLEAR(str2);
|
|
return str2;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.valid_encoding? -> true or false
|
|
*
|
|
* Returns true for a string which is encoded correctly.
|
|
*
|
|
* "\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
|
|
rb_str_valid_encoding_p(VALUE str)
|
|
{
|
|
int cr = rb_enc_str_coderange(str);
|
|
|
|
return cr == ENC_CODERANGE_BROKEN ? Qfalse : Qtrue;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.ascii_only? -> true or false
|
|
*
|
|
* Returns true for a string which has only ASCII characters.
|
|
*
|
|
* "abc".force_encoding("UTF-8").ascii_only? #=> true
|
|
* "abc\u{6666}".force_encoding("UTF-8").ascii_only? #=> false
|
|
*/
|
|
|
|
static VALUE
|
|
rb_str_is_ascii_only_p(VALUE str)
|
|
{
|
|
int cr = rb_enc_str_coderange(str);
|
|
|
|
return cr == ENC_CODERANGE_7BIT ? Qtrue : Qfalse;
|
|
}
|
|
|
|
/**
|
|
* Shortens _str_ and adds three dots, an ellipsis, if it is longer
|
|
* than _len_ characters.
|
|
*
|
|
* \param str the string to ellipsize.
|
|
* \param len the maximum string length.
|
|
* \return the ellipsized string.
|
|
* \pre _len_ must not be negative.
|
|
* \post the length of the returned string in characters is less than or equal to _len_.
|
|
* \post If the length of _str_ is less than or equal _len_, returns _str_ itself.
|
|
* \post the encoding of returned string is equal to the encoding of _str_.
|
|
* \post the class of returned string is equal to the class of _str_.
|
|
* \note the length is counted in characters.
|
|
*/
|
|
VALUE
|
|
rb_str_ellipsize(VALUE str, long len)
|
|
{
|
|
static const char ellipsis[] = "...";
|
|
const long ellipsislen = sizeof(ellipsis) - 1;
|
|
rb_encoding *const enc = rb_enc_get(str);
|
|
const long blen = RSTRING_LEN(str);
|
|
const char *const p = RSTRING_PTR(str), *e = p + blen;
|
|
VALUE estr, ret = 0;
|
|
|
|
if (len < 0) rb_raise(rb_eIndexError, "negative length %ld", len);
|
|
if (len * rb_enc_mbminlen(enc) >= blen ||
|
|
(e = rb_enc_nth(p, e, len, enc)) - p == blen) {
|
|
ret = str;
|
|
}
|
|
else if (len <= ellipsislen ||
|
|
!(e = rb_enc_step_back(p, e, e, len = ellipsislen, enc))) {
|
|
if (rb_enc_asciicompat(enc)) {
|
|
ret = rb_str_new(ellipsis, len);
|
|
rb_enc_associate(ret, enc);
|
|
}
|
|
else {
|
|
estr = rb_usascii_str_new(ellipsis, len);
|
|
ret = rb_str_encode(estr, rb_enc_from_encoding(enc), 0, Qnil);
|
|
}
|
|
}
|
|
else if (ret = rb_str_subseq(str, 0, e - p), rb_enc_asciicompat(enc)) {
|
|
rb_str_cat(ret, ellipsis, ellipsislen);
|
|
}
|
|
else {
|
|
estr = rb_str_encode(rb_usascii_str_new(ellipsis, ellipsislen),
|
|
rb_enc_from_encoding(enc), 0, Qnil);
|
|
rb_str_append(ret, estr);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static VALUE
|
|
str_compat_and_valid(VALUE str, rb_encoding *enc)
|
|
{
|
|
int cr;
|
|
str = StringValue(str);
|
|
cr = rb_enc_str_coderange(str);
|
|
if (cr == ENC_CODERANGE_BROKEN) {
|
|
rb_raise(rb_eArgError, "replacement must be valid byte sequence '%+"PRIsVALUE"'", str);
|
|
}
|
|
else {
|
|
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));
|
|
}
|
|
}
|
|
return str;
|
|
}
|
|
|
|
static VALUE enc_str_scrub(rb_encoding *enc, VALUE str, VALUE repl, int cr);
|
|
|
|
/**
|
|
* @param str the string to be scrubbed
|
|
* @param repl the replacement character
|
|
* @return If given string is invalid, returns a new string. Otherwise, returns Qnil.
|
|
*/
|
|
VALUE
|
|
rb_str_scrub(VALUE str, VALUE repl)
|
|
{
|
|
rb_encoding *enc = STR_ENC_GET(str);
|
|
return enc_str_scrub(enc, str, repl, ENC_CODERANGE(str));
|
|
}
|
|
|
|
VALUE
|
|
rb_enc_str_scrub(rb_encoding *enc, VALUE str, VALUE repl)
|
|
{
|
|
int cr = ENC_CODERANGE_UNKNOWN;
|
|
if (enc == STR_ENC_GET(str)) {
|
|
/* cached coderange makes sense only when enc equals the
|
|
* actual encoding of str */
|
|
cr = ENC_CODERANGE(str);
|
|
}
|
|
return enc_str_scrub(enc, str, repl, cr);
|
|
}
|
|
|
|
static VALUE
|
|
enc_str_scrub(rb_encoding *enc, VALUE str, VALUE repl, int cr)
|
|
{
|
|
int encidx;
|
|
VALUE buf = Qnil;
|
|
const char *rep, *p, *e, *p1, *sp;
|
|
long replen = -1;
|
|
long slen;
|
|
|
|
if (rb_block_given_p()) {
|
|
if (!NIL_P(repl))
|
|
rb_raise(rb_eArgError, "both of block and replacement given");
|
|
replen = 0;
|
|
}
|
|
|
|
if (ENC_CODERANGE_CLEAN_P(cr))
|
|
return Qnil;
|
|
|
|
if (!NIL_P(repl)) {
|
|
repl = str_compat_and_valid(repl, enc);
|
|
}
|
|
|
|
if (rb_enc_dummy_p(enc)) {
|
|
return Qnil;
|
|
}
|
|
encidx = rb_enc_to_index(enc);
|
|
|
|
#define DEFAULT_REPLACE_CHAR(str) do { \
|
|
static const char replace[sizeof(str)-1] = str; \
|
|
rep = replace; replen = (int)sizeof(replace); \
|
|
} while (0)
|
|
|
|
slen = RSTRING_LEN(str);
|
|
p = RSTRING_PTR(str);
|
|
e = RSTRING_END(str);
|
|
p1 = p;
|
|
sp = p;
|
|
|
|
if (rb_enc_asciicompat(enc)) {
|
|
int rep7bit_p;
|
|
if (!replen) {
|
|
rep = NULL;
|
|
rep7bit_p = FALSE;
|
|
}
|
|
else if (!NIL_P(repl)) {
|
|
rep = RSTRING_PTR(repl);
|
|
replen = RSTRING_LEN(repl);
|
|
rep7bit_p = (ENC_CODERANGE(repl) == ENC_CODERANGE_7BIT);
|
|
}
|
|
else if (encidx == rb_utf8_encindex()) {
|
|
DEFAULT_REPLACE_CHAR("\xEF\xBF\xBD");
|
|
rep7bit_p = FALSE;
|
|
}
|
|
else {
|
|
DEFAULT_REPLACE_CHAR("?");
|
|
rep7bit_p = TRUE;
|
|
}
|
|
cr = ENC_CODERANGE_7BIT;
|
|
|
|
p = search_nonascii(p, e);
|
|
if (!p) {
|
|
p = e;
|
|
}
|
|
while (p < e) {
|
|
int ret = rb_enc_precise_mbclen(p, e, enc);
|
|
if (MBCLEN_NEEDMORE_P(ret)) {
|
|
break;
|
|
}
|
|
else if (MBCLEN_CHARFOUND_P(ret)) {
|
|
cr = ENC_CODERANGE_VALID;
|
|
p += MBCLEN_CHARFOUND_LEN(ret);
|
|
}
|
|
else if (MBCLEN_INVALID_P(ret)) {
|
|
/*
|
|
* p1~p: valid ascii/multibyte chars
|
|
* p ~e: invalid bytes + unknown bytes
|
|
*/
|
|
long clen = rb_enc_mbmaxlen(enc);
|
|
if (NIL_P(buf)) buf = rb_str_buf_new(RSTRING_LEN(str));
|
|
if (p > p1) {
|
|
rb_str_buf_cat(buf, p1, p - p1);
|
|
}
|
|
|
|
if (e - p < clen) clen = e - p;
|
|
if (clen <= 2) {
|
|
clen = 1;
|
|
}
|
|
else {
|
|
const char *q = p;
|
|
clen--;
|
|
for (; clen > 1; clen--) {
|
|
ret = rb_enc_precise_mbclen(q, q + clen, enc);
|
|
if (MBCLEN_NEEDMORE_P(ret)) break;
|
|
if (MBCLEN_INVALID_P(ret)) continue;
|
|
UNREACHABLE;
|
|
}
|
|
}
|
|
if (rep) {
|
|
rb_str_buf_cat(buf, rep, replen);
|
|
if (!rep7bit_p) cr = ENC_CODERANGE_VALID;
|
|
}
|
|
else {
|
|
repl = rb_yield(rb_enc_str_new(p, clen, enc));
|
|
str_mod_check(str, sp, slen);
|
|
repl = str_compat_and_valid(repl, enc);
|
|
rb_str_buf_cat(buf, RSTRING_PTR(repl), RSTRING_LEN(repl));
|
|
if (ENC_CODERANGE(repl) == ENC_CODERANGE_VALID)
|
|
cr = ENC_CODERANGE_VALID;
|
|
}
|
|
p += clen;
|
|
p1 = p;
|
|
p = search_nonascii(p, e);
|
|
if (!p) {
|
|
p = e;
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
UNREACHABLE;
|
|
}
|
|
}
|
|
if (NIL_P(buf)) {
|
|
if (p == e) {
|
|
ENC_CODERANGE_SET(str, cr);
|
|
return Qnil;
|
|
}
|
|
buf = rb_str_buf_new(RSTRING_LEN(str));
|
|
}
|
|
if (p1 < p) {
|
|
rb_str_buf_cat(buf, p1, p - p1);
|
|
}
|
|
if (p < e) {
|
|
if (rep) {
|
|
rb_str_buf_cat(buf, rep, replen);
|
|
if (!rep7bit_p) cr = ENC_CODERANGE_VALID;
|
|
}
|
|
else {
|
|
repl = rb_yield(rb_enc_str_new(p, e-p, enc));
|
|
str_mod_check(str, sp, slen);
|
|
repl = str_compat_and_valid(repl, enc);
|
|
rb_str_buf_cat(buf, RSTRING_PTR(repl), RSTRING_LEN(repl));
|
|
if (ENC_CODERANGE(repl) == ENC_CODERANGE_VALID)
|
|
cr = ENC_CODERANGE_VALID;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
/* ASCII incompatible */
|
|
long mbminlen = rb_enc_mbminlen(enc);
|
|
if (!replen) {
|
|
rep = NULL;
|
|
}
|
|
else if (!NIL_P(repl)) {
|
|
rep = RSTRING_PTR(repl);
|
|
replen = RSTRING_LEN(repl);
|
|
}
|
|
else if (encidx == ENCINDEX_UTF_16BE) {
|
|
DEFAULT_REPLACE_CHAR("\xFF\xFD");
|
|
}
|
|
else if (encidx == ENCINDEX_UTF_16LE) {
|
|
DEFAULT_REPLACE_CHAR("\xFD\xFF");
|
|
}
|
|
else if (encidx == ENCINDEX_UTF_32BE) {
|
|
DEFAULT_REPLACE_CHAR("\x00\x00\xFF\xFD");
|
|
}
|
|
else if (encidx == ENCINDEX_UTF_32LE) {
|
|
DEFAULT_REPLACE_CHAR("\xFD\xFF\x00\x00");
|
|
}
|
|
else {
|
|
DEFAULT_REPLACE_CHAR("?");
|
|
}
|
|
|
|
while (p < e) {
|
|
int ret = rb_enc_precise_mbclen(p, e, enc);
|
|
if (MBCLEN_NEEDMORE_P(ret)) {
|
|
break;
|
|
}
|
|
else if (MBCLEN_CHARFOUND_P(ret)) {
|
|
p += MBCLEN_CHARFOUND_LEN(ret);
|
|
}
|
|
else if (MBCLEN_INVALID_P(ret)) {
|
|
const char *q = p;
|
|
long clen = rb_enc_mbmaxlen(enc);
|
|
if (NIL_P(buf)) buf = rb_str_buf_new(RSTRING_LEN(str));
|
|
if (p > p1) rb_str_buf_cat(buf, p1, p - p1);
|
|
|
|
if (e - p < clen) clen = e - p;
|
|
if (clen <= mbminlen * 2) {
|
|
clen = mbminlen;
|
|
}
|
|
else {
|
|
clen -= mbminlen;
|
|
for (; clen > mbminlen; clen-=mbminlen) {
|
|
ret = rb_enc_precise_mbclen(q, q + clen, enc);
|
|
if (MBCLEN_NEEDMORE_P(ret)) break;
|
|
if (MBCLEN_INVALID_P(ret)) continue;
|
|
UNREACHABLE;
|
|
}
|
|
}
|
|
if (rep) {
|
|
rb_str_buf_cat(buf, rep, replen);
|
|
}
|
|
else {
|
|
repl = rb_yield(rb_enc_str_new(p, clen, enc));
|
|
str_mod_check(str, sp, slen);
|
|
repl = str_compat_and_valid(repl, enc);
|
|
rb_str_buf_cat(buf, RSTRING_PTR(repl), RSTRING_LEN(repl));
|
|
}
|
|
p += clen;
|
|
p1 = p;
|
|
}
|
|
else {
|
|
UNREACHABLE;
|
|
}
|
|
}
|
|
if (NIL_P(buf)) {
|
|
if (p == e) {
|
|
ENC_CODERANGE_SET(str, ENC_CODERANGE_VALID);
|
|
return Qnil;
|
|
}
|
|
buf = rb_str_buf_new(RSTRING_LEN(str));
|
|
}
|
|
if (p1 < p) {
|
|
rb_str_buf_cat(buf, p1, p - p1);
|
|
}
|
|
if (p < e) {
|
|
if (rep) {
|
|
rb_str_buf_cat(buf, rep, replen);
|
|
}
|
|
else {
|
|
repl = rb_yield(rb_enc_str_new(p, e-p, enc));
|
|
str_mod_check(str, sp, slen);
|
|
repl = str_compat_and_valid(repl, enc);
|
|
rb_str_buf_cat(buf, RSTRING_PTR(repl), RSTRING_LEN(repl));
|
|
}
|
|
}
|
|
cr = ENC_CODERANGE_VALID;
|
|
}
|
|
ENCODING_CODERANGE_SET(buf, rb_enc_to_index(enc), cr);
|
|
return buf;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.scrub -> new_str
|
|
* str.scrub(repl) -> new_str
|
|
* str.scrub{|bytes|} -> new_str
|
|
*
|
|
* If the string is invalid byte sequence then replace invalid bytes with given replacement
|
|
* character, else returns self.
|
|
* If block is given, replace invalid bytes with returned value of the block.
|
|
*
|
|
* "abc\u3042\x81".scrub #=> "abc\u3042\uFFFD"
|
|
* "abc\u3042\x81".scrub("*") #=> "abc\u3042*"
|
|
* "abc\u3042\xE3\x80".scrub{|bytes| '<'+bytes.unpack('H*')[0]+'>' } #=> "abc\u3042<e380>"
|
|
*/
|
|
static VALUE
|
|
str_scrub(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
VALUE repl = argc ? (rb_check_arity(argc, 0, 1), argv[0]) : Qnil;
|
|
VALUE new = rb_str_scrub(str, repl);
|
|
return NIL_P(new) ? str_duplicate(rb_cString, str): new;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.scrub! -> str
|
|
* str.scrub!(repl) -> str
|
|
* str.scrub!{|bytes|} -> str
|
|
*
|
|
* If the string is invalid byte sequence then replace invalid bytes with given replacement
|
|
* character, else returns self.
|
|
* If block is given, replace invalid bytes with returned value of the block.
|
|
*
|
|
* "abc\u3042\x81".scrub! #=> "abc\u3042\uFFFD"
|
|
* "abc\u3042\x81".scrub!("*") #=> "abc\u3042*"
|
|
* "abc\u3042\xE3\x80".scrub!{|bytes| '<'+bytes.unpack('H*')[0]+'>' } #=> "abc\u3042<e380>"
|
|
*/
|
|
static VALUE
|
|
str_scrub_bang(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
VALUE repl = argc ? (rb_check_arity(argc, 0, 1), argv[0]) : Qnil;
|
|
VALUE new = rb_str_scrub(str, repl);
|
|
if (!NIL_P(new)) rb_str_replace(str, new);
|
|
return str;
|
|
}
|
|
|
|
static ID id_normalize;
|
|
static ID id_normalized_p;
|
|
static VALUE mUnicodeNormalize;
|
|
|
|
static VALUE
|
|
unicode_normalize_common(int argc, VALUE *argv, VALUE str, ID id)
|
|
{
|
|
static int UnicodeNormalizeRequired = 0;
|
|
VALUE argv2[2];
|
|
|
|
if (!UnicodeNormalizeRequired) {
|
|
rb_require("unicode_normalize/normalize.rb");
|
|
UnicodeNormalizeRequired = 1;
|
|
}
|
|
argv2[0] = str;
|
|
if (rb_check_arity(argc, 0, 1)) argv2[1] = argv[0];
|
|
return rb_funcallv(mUnicodeNormalize, id, argc+1, argv2);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.unicode_normalize(form=:nfc)
|
|
*
|
|
* Unicode Normalization---Returns a normalized form of +str+,
|
|
* using Unicode normalizations NFC, NFD, NFKC, or NFKD.
|
|
* The normalization form used is determined by +form+, which can
|
|
* be any of the four values +:nfc+, +:nfd+, +:nfkc+, or +:nfkd+.
|
|
* The default is +:nfc+.
|
|
*
|
|
* If the string is not in a Unicode Encoding, then an Exception is raised.
|
|
* In this context, 'Unicode Encoding' means any of UTF-8, UTF-16BE/LE,
|
|
* and UTF-32BE/LE, as well as GB18030, UCS_2BE, and UCS_4BE.
|
|
* Anything other than UTF-8 is implemented by converting to UTF-8,
|
|
* which makes it slower than UTF-8.
|
|
*
|
|
* "a\u0300".unicode_normalize #=> "\u00E0"
|
|
* "a\u0300".unicode_normalize(:nfc) #=> "\u00E0"
|
|
* "\u00E0".unicode_normalize(:nfd) #=> "a\u0300"
|
|
* "\xE0".force_encoding('ISO-8859-1').unicode_normalize(:nfd)
|
|
* #=> Encoding::CompatibilityError raised
|
|
*/
|
|
static VALUE
|
|
rb_str_unicode_normalize(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
return unicode_normalize_common(argc, argv, str, id_normalize);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* str.unicode_normalize!(form=:nfc)
|
|
*
|
|
* Destructive version of String#unicode_normalize, doing Unicode
|
|
* normalization in place.
|
|
*/
|
|
static VALUE
|
|
rb_str_unicode_normalize_bang(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
return rb_str_replace(str, unicode_normalize_common(argc, argv, str, id_normalize));
|
|
}
|
|
|
|
/* call-seq:
|
|
* str.unicode_normalized?(form=:nfc)
|
|
*
|
|
* Checks whether +str+ is in Unicode normalization form +form+,
|
|
* which can be any of the four values +:nfc+, +:nfd+, +:nfkc+, or +:nfkd+.
|
|
* The default is +:nfc+.
|
|
*
|
|
* If the string is not in a Unicode Encoding, then an Exception is raised.
|
|
* For details, see String#unicode_normalize.
|
|
*
|
|
* "a\u0300".unicode_normalized? #=> false
|
|
* "a\u0300".unicode_normalized?(:nfd) #=> true
|
|
* "\u00E0".unicode_normalized? #=> true
|
|
* "\u00E0".unicode_normalized?(:nfd) #=> false
|
|
* "\xE0".force_encoding('ISO-8859-1').unicode_normalized?
|
|
* #=> Encoding::CompatibilityError raised
|
|
*/
|
|
static VALUE
|
|
rb_str_unicode_normalized_p(int argc, VALUE *argv, VALUE str)
|
|
{
|
|
return unicode_normalize_common(argc, argv, str, id_normalized_p);
|
|
}
|
|
|
|
/**********************************************************************
|
|
* Document-class: Symbol
|
|
*
|
|
* Symbol objects represent names inside the Ruby interpreter. They
|
|
* are generated using the <code>:name</code> and
|
|
* <code>:"string"</code> literals syntax, and by the various
|
|
* <code>to_sym</code> methods. 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>
|
|
* will be the same object in all three contexts.
|
|
*
|
|
* module One
|
|
* class Fred
|
|
* end
|
|
* $f1 = :Fred
|
|
* end
|
|
* module Two
|
|
* Fred = 1
|
|
* $f2 = :Fred
|
|
* end
|
|
* def Fred()
|
|
* end
|
|
* $f3 = :Fred
|
|
* $f1.object_id #=> 2514190
|
|
* $f2.object_id #=> 2514190
|
|
* $f3.object_id #=> 2514190
|
|
*
|
|
*/
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* sym == obj -> true or false
|
|
*
|
|
* Equality---If <i>sym</i> and <i>obj</i> are exactly the same
|
|
* symbol, returns <code>true</code>.
|
|
*/
|
|
|
|
#define sym_equal rb_obj_equal
|
|
|
|
static int
|
|
sym_printable(const char *s, const char *send, rb_encoding *enc)
|
|
{
|
|
while (s < send) {
|
|
int n;
|
|
int c = rb_enc_precise_mbclen(s, send, enc);
|
|
|
|
if (!MBCLEN_CHARFOUND_P(c)) return FALSE;
|
|
n = MBCLEN_CHARFOUND_LEN(c);
|
|
c = rb_enc_mbc_to_codepoint(s, send, enc);
|
|
if (!rb_enc_isprint(c, enc)) return FALSE;
|
|
s += n;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
int
|
|
rb_str_symname_p(VALUE sym)
|
|
{
|
|
rb_encoding *enc;
|
|
const char *ptr;
|
|
long len;
|
|
rb_encoding *resenc = rb_default_internal_encoding();
|
|
|
|
if (resenc == NULL) resenc = rb_default_external_encoding();
|
|
enc = STR_ENC_GET(sym);
|
|
ptr = RSTRING_PTR(sym);
|
|
len = RSTRING_LEN(sym);
|
|
if ((resenc != enc && !rb_str_is_ascii_only_p(sym)) || len != (long)strlen(ptr) ||
|
|
!rb_enc_symname2_p(ptr, len, enc) || !sym_printable(ptr, ptr + len, enc)) {
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
VALUE
|
|
rb_str_quote_unprintable(VALUE str)
|
|
{
|
|
rb_encoding *enc;
|
|
const char *ptr;
|
|
long len;
|
|
rb_encoding *resenc;
|
|
|
|
Check_Type(str, T_STRING);
|
|
resenc = rb_default_internal_encoding();
|
|
if (resenc == NULL) resenc = rb_default_external_encoding();
|
|
enc = STR_ENC_GET(str);
|
|
ptr = RSTRING_PTR(str);
|
|
len = RSTRING_LEN(str);
|
|
if ((resenc != enc && !rb_str_is_ascii_only_p(str)) ||
|
|
!sym_printable(ptr, ptr + len, enc)) {
|
|
return rb_str_inspect(str);
|
|
}
|
|
return str;
|
|
}
|
|
|
|
MJIT_FUNC_EXPORTED VALUE
|
|
rb_id_quote_unprintable(ID id)
|
|
{
|
|
VALUE str = rb_id2str(id);
|
|
if (!rb_str_symname_p(str)) {
|
|
return rb_str_inspect(str);
|
|
}
|
|
return str;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* sym.inspect -> string
|
|
*
|
|
* Returns the representation of <i>sym</i> as a symbol literal.
|
|
*
|
|
* :fred.inspect #=> ":fred"
|
|
*/
|
|
|
|
static VALUE
|
|
sym_inspect(VALUE sym)
|
|
{
|
|
VALUE str = rb_sym2str(sym);
|
|
const char *ptr;
|
|
long len;
|
|
char *dest;
|
|
|
|
if (!rb_str_symname_p(str)) {
|
|
str = rb_str_inspect(str);
|
|
len = RSTRING_LEN(str);
|
|
rb_str_resize(str, len + 1);
|
|
dest = RSTRING_PTR(str);
|
|
memmove(dest + 1, dest, len);
|
|
}
|
|
else {
|
|
rb_encoding *enc = STR_ENC_GET(str);
|
|
RSTRING_GETMEM(str, ptr, len);
|
|
str = rb_enc_str_new(0, len + 1, enc);
|
|
dest = RSTRING_PTR(str);
|
|
memcpy(dest + 1, ptr, len);
|
|
}
|
|
dest[0] = ':';
|
|
return str;
|
|
}
|
|
|
|
#if 0 /* for RDoc */
|
|
/*
|
|
* call-seq:
|
|
* sym.name -> string
|
|
*
|
|
* Returns the name or string corresponding to <i>sym</i>. Unlike #to_s, the
|
|
* returned string is frozen.
|
|
*
|
|
* :fred.name #=> "fred"
|
|
* :fred.name.frozen? #=> true
|
|
* :fred.to_s #=> "fred"
|
|
* :fred.to_s.frozen? #=> false
|
|
*/
|
|
VALUE
|
|
rb_sym2str(VALUE sym)
|
|
{
|
|
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* sym.id2name -> string
|
|
* sym.to_s -> string
|
|
*
|
|
* Returns the name or string corresponding to <i>sym</i>.
|
|
*
|
|
* :fred.id2name #=> "fred"
|
|
* :ginger.to_s #=> "ginger"
|
|
*
|
|
* Note that this string is not frozen (unlike the symbol itself).
|
|
* To get a frozen string, use #name.
|
|
*/
|
|
|
|
|
|
VALUE
|
|
rb_sym_to_s(VALUE sym)
|
|
{
|
|
return str_new_shared(rb_cString, rb_sym2str(sym));
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* sym.to_sym -> sym
|
|
* sym.intern -> sym
|
|
*
|
|
* In general, <code>to_sym</code> returns the Symbol corresponding
|
|
* to an object. As <i>sym</i> is already a symbol, <code>self</code> is returned
|
|
* in this case.
|
|
*/
|
|
|
|
static VALUE
|
|
sym_to_sym(VALUE sym)
|
|
{
|
|
return sym;
|
|
}
|
|
|
|
MJIT_FUNC_EXPORTED VALUE
|
|
rb_sym_proc_call(ID mid, int argc, const VALUE *argv, int kw_splat, VALUE passed_proc)
|
|
{
|
|
VALUE obj;
|
|
|
|
if (argc < 1) {
|
|
rb_raise(rb_eArgError, "no receiver given");
|
|
}
|
|
obj = argv[0];
|
|
return rb_funcall_with_block_kw(obj, mid, argc - 1, argv + 1, passed_proc, kw_splat);
|
|
}
|
|
|
|
#if 0
|
|
/*
|
|
* call-seq:
|
|
* sym.to_proc
|
|
*
|
|
* Returns a _Proc_ object which responds to the given method by _sym_.
|
|
*
|
|
* (1..3).collect(&:to_s) #=> ["1", "2", "3"]
|
|
*/
|
|
|
|
VALUE
|
|
rb_sym_to_proc(VALUE sym)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* call-seq:
|
|
*
|
|
* sym.succ
|
|
*
|
|
* Same as <code>sym.to_s.succ.intern</code>.
|
|
*/
|
|
|
|
static VALUE
|
|
sym_succ(VALUE sym)
|
|
{
|
|
return rb_str_intern(rb_str_succ(rb_sym2str(sym)));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
*
|
|
* symbol <=> other_symbol -> -1, 0, +1, or nil
|
|
*
|
|
* Compares +symbol+ with +other_symbol+ after calling #to_s on each of the
|
|
* symbols. Returns -1, 0, +1, or +nil+ depending on whether +symbol+ is
|
|
* less than, equal to, or greater than +other_symbol+.
|
|
*
|
|
* +nil+ is returned if the two values are incomparable.
|
|
*
|
|
* See String#<=> for more information.
|
|
*/
|
|
|
|
static VALUE
|
|
sym_cmp(VALUE sym, VALUE other)
|
|
{
|
|
if (!SYMBOL_P(other)) {
|
|
return Qnil;
|
|
}
|
|
return rb_str_cmp_m(rb_sym2str(sym), rb_sym2str(other));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* sym.casecmp(other_symbol) -> -1, 0, +1, or nil
|
|
*
|
|
* Case-insensitive version of Symbol#<=>.
|
|
* Currently, case-insensitivity only works on characters A-Z/a-z,
|
|
* not all of Unicode. This is different from Symbol#casecmp?.
|
|
*
|
|
* :aBcDeF.casecmp(:abcde) #=> 1
|
|
* :aBcDeF.casecmp(:abcdef) #=> 0
|
|
* :aBcDeF.casecmp(:abcdefg) #=> -1
|
|
* :abcdef.casecmp(:ABCDEF) #=> 0
|
|
*
|
|
* +nil+ is returned if the two symbols have incompatible encodings,
|
|
* or if +other_symbol+ is not a symbol.
|
|
*
|
|
* :foo.casecmp(2) #=> nil
|
|
* "\u{e4 f6 fc}".encode("ISO-8859-1").to_sym.casecmp(:"\u{c4 d6 dc}") #=> nil
|
|
*/
|
|
|
|
static VALUE
|
|
sym_casecmp(VALUE sym, VALUE other)
|
|
{
|
|
if (!SYMBOL_P(other)) {
|
|
return Qnil;
|
|
}
|
|
return str_casecmp(rb_sym2str(sym), rb_sym2str(other));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* sym.casecmp?(other_symbol) -> true, false, or nil
|
|
*
|
|
* Returns +true+ if +sym+ and +other_symbol+ are equal after
|
|
* Unicode case folding, +false+ if they are not equal.
|
|
*
|
|
* :aBcDeF.casecmp?(:abcde) #=> false
|
|
* :aBcDeF.casecmp?(:abcdef) #=> true
|
|
* :aBcDeF.casecmp?(:abcdefg) #=> false
|
|
* :abcdef.casecmp?(:ABCDEF) #=> true
|
|
* :"\u{e4 f6 fc}".casecmp?(:"\u{c4 d6 dc}") #=> true
|
|
*
|
|
* +nil+ is returned if the two symbols have incompatible encodings,
|
|
* or if +other_symbol+ is not a symbol.
|
|
*
|
|
* :foo.casecmp?(2) #=> nil
|
|
* "\u{e4 f6 fc}".encode("ISO-8859-1").to_sym.casecmp?(:"\u{c4 d6 dc}") #=> nil
|
|
*/
|
|
|
|
static VALUE
|
|
sym_casecmp_p(VALUE sym, VALUE other)
|
|
{
|
|
if (!SYMBOL_P(other)) {
|
|
return Qnil;
|
|
}
|
|
return str_casecmp_p(rb_sym2str(sym), rb_sym2str(other));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* sym =~ obj -> integer or nil
|
|
*
|
|
* Returns <code>sym.to_s =~ obj</code>.
|
|
*/
|
|
|
|
static VALUE
|
|
sym_match(VALUE sym, VALUE other)
|
|
{
|
|
return rb_str_match(rb_sym2str(sym), other);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* sym.match(pattern) -> matchdata or nil
|
|
* sym.match(pattern, pos) -> matchdata or nil
|
|
*
|
|
* Returns <code>sym.to_s.match</code>.
|
|
*/
|
|
|
|
static VALUE
|
|
sym_match_m(int argc, VALUE *argv, VALUE sym)
|
|
{
|
|
return rb_str_match_m(argc, argv, rb_sym2str(sym));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* sym.match?(pattern) -> true or false
|
|
* sym.match?(pattern, pos) -> true or false
|
|
*
|
|
* Returns <code>sym.to_s.match?</code>.
|
|
*/
|
|
|
|
static VALUE
|
|
sym_match_m_p(int argc, VALUE *argv, VALUE sym)
|
|
{
|
|
return rb_str_match_m_p(argc, argv, sym);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* sym[idx] -> char
|
|
* sym[b, n] -> string
|
|
* sym.slice(idx) -> char
|
|
* sym.slice(b, n) -> string
|
|
*
|
|
* Returns <code>sym.to_s[]</code>.
|
|
*/
|
|
|
|
static VALUE
|
|
sym_aref(int argc, VALUE *argv, VALUE sym)
|
|
{
|
|
return rb_str_aref_m(argc, argv, rb_sym2str(sym));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* sym.length -> integer
|
|
* sym.size -> integer
|
|
*
|
|
* Same as <code>sym.to_s.length</code>.
|
|
*/
|
|
|
|
static VALUE
|
|
sym_length(VALUE sym)
|
|
{
|
|
return rb_str_length(rb_sym2str(sym));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* sym.empty? -> true or false
|
|
*
|
|
* Returns whether _sym_ is :"" or not.
|
|
*/
|
|
|
|
static VALUE
|
|
sym_empty(VALUE sym)
|
|
{
|
|
return rb_str_empty(rb_sym2str(sym));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* sym.upcase -> symbol
|
|
* sym.upcase([options]) -> symbol
|
|
*
|
|
* Same as <code>sym.to_s.upcase.intern</code>.
|
|
*/
|
|
|
|
static VALUE
|
|
sym_upcase(int argc, VALUE *argv, VALUE sym)
|
|
{
|
|
return rb_str_intern(rb_str_upcase(argc, argv, rb_sym2str(sym)));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* sym.downcase -> symbol
|
|
* sym.downcase([options]) -> symbol
|
|
*
|
|
* Same as <code>sym.to_s.downcase.intern</code>.
|
|
*/
|
|
|
|
static VALUE
|
|
sym_downcase(int argc, VALUE *argv, VALUE sym)
|
|
{
|
|
return rb_str_intern(rb_str_downcase(argc, argv, rb_sym2str(sym)));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* sym.capitalize -> symbol
|
|
* sym.capitalize([options]) -> symbol
|
|
*
|
|
* Same as <code>sym.to_s.capitalize.intern</code>.
|
|
*/
|
|
|
|
static VALUE
|
|
sym_capitalize(int argc, VALUE *argv, VALUE sym)
|
|
{
|
|
return rb_str_intern(rb_str_capitalize(argc, argv, rb_sym2str(sym)));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* sym.swapcase -> symbol
|
|
* sym.swapcase([options]) -> symbol
|
|
*
|
|
* Same as <code>sym.to_s.swapcase.intern</code>.
|
|
*/
|
|
|
|
static VALUE
|
|
sym_swapcase(int argc, VALUE *argv, VALUE sym)
|
|
{
|
|
return rb_str_intern(rb_str_swapcase(argc, argv, rb_sym2str(sym)));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* sym.start_with?([prefixes]+) -> true or false
|
|
*
|
|
* Returns true if +sym+ starts with one of the +prefixes+ given.
|
|
* Each of the +prefixes+ should be a String or a Regexp.
|
|
*
|
|
* :hello.start_with?("hell") #=> true
|
|
* :hello.start_with?(/H/i) #=> true
|
|
*
|
|
* # returns true if one of the prefixes matches.
|
|
* :hello.start_with?("heaven", "hell") #=> true
|
|
* :hello.start_with?("heaven", "paradise") #=> false
|
|
*/
|
|
|
|
static VALUE
|
|
sym_start_with(int argc, VALUE *argv, VALUE sym)
|
|
{
|
|
return rb_str_start_with(argc, argv, rb_sym2str(sym));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* sym.end_with?([suffixes]+) -> true or false
|
|
*
|
|
* Returns true if +sym+ ends with one of the +suffixes+ given.
|
|
*
|
|
* :hello.end_with?("ello") #=> true
|
|
*
|
|
* # returns true if one of the +suffixes+ matches.
|
|
* :hello.end_with?("heaven", "ello") #=> true
|
|
* :hello.end_with?("heaven", "paradise") #=> false
|
|
*/
|
|
|
|
static VALUE
|
|
sym_end_with(int argc, VALUE *argv, VALUE sym)
|
|
{
|
|
return rb_str_end_with(argc, argv, rb_sym2str(sym));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* sym.encoding -> encoding
|
|
*
|
|
* Returns the Encoding object that represents the encoding of _sym_.
|
|
*/
|
|
|
|
static VALUE
|
|
sym_encoding(VALUE sym)
|
|
{
|
|
return rb_obj_encoding(rb_sym2str(sym));
|
|
}
|
|
|
|
static VALUE
|
|
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",
|
|
name);
|
|
}
|
|
name = tmp;
|
|
}
|
|
return name;
|
|
}
|
|
|
|
ID
|
|
rb_to_id(VALUE name)
|
|
{
|
|
if (SYMBOL_P(name)) {
|
|
return SYM2ID(name);
|
|
}
|
|
name = string_for_symbol(name);
|
|
return rb_intern_str(name);
|
|
}
|
|
|
|
VALUE
|
|
rb_to_symbol(VALUE name)
|
|
{
|
|
if (SYMBOL_P(name)) {
|
|
return name;
|
|
}
|
|
name = string_for_symbol(name);
|
|
return rb_str_intern(name);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* Symbol.all_symbols => array
|
|
*
|
|
* Returns an array of all the symbols currently in Ruby's symbol
|
|
* table.
|
|
*
|
|
* Symbol.all_symbols.size #=> 903
|
|
* Symbol.all_symbols[1,20] #=> [:floor, :ARGV, :Binding, :symlink,
|
|
* :chown, :EOFError, :$;, :String,
|
|
* :LOCK_SH, :"setuid?", :$<,
|
|
* :default_proc, :compact, :extend,
|
|
* :Tms, :getwd, :$=, :ThreadGroup,
|
|
* :wait2, :$>]
|
|
*/
|
|
|
|
static VALUE
|
|
sym_all_symbols(VALUE _)
|
|
{
|
|
return rb_sym_all_symbols();
|
|
}
|
|
|
|
VALUE
|
|
rb_str_to_interned_str(VALUE str)
|
|
{
|
|
return rb_fstring(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);
|
|
}
|
|
|
|
VALUE
|
|
rb_interned_str_cstr(const char *ptr)
|
|
{
|
|
return rb_interned_str(ptr, strlen(ptr));
|
|
}
|
|
|
|
VALUE
|
|
rb_enc_interned_str(const char *ptr, long len, rb_encoding *enc)
|
|
{
|
|
struct RString fake_str;
|
|
return register_fstring(rb_setup_fake_str(&fake_str, ptr, len, enc), TRUE);
|
|
}
|
|
|
|
VALUE
|
|
rb_enc_interned_str_cstr(const char *ptr, rb_encoding *enc)
|
|
{
|
|
return rb_enc_interned_str(ptr, strlen(ptr), enc);
|
|
}
|
|
|
|
/*
|
|
* A String object holds and manipulates an arbitrary sequence of
|
|
* bytes, typically representing characters. String objects may be created
|
|
* using String::new or as literals.
|
|
*
|
|
* Because of aliasing issues, users of strings should be aware of the methods
|
|
* that modify the contents of a String object. Typically,
|
|
* methods with names ending in ``!'' modify their receiver, while those
|
|
* without a ``!'' return a new String. However, there are
|
|
* exceptions, such as String#[]=.
|
|
*
|
|
* == What's Here
|
|
*
|
|
* First, what's elsewhere. \String includes the module Comparable,
|
|
* which provides several very useful methods.
|
|
*
|
|
* Here, class \String provides methods that are useful for:
|
|
*
|
|
* - {Creating a String}[#class-String-label-Methods+for+Creating+a+String]
|
|
* - {Frozen/Unfrozen Strings}[#class-String-label-Methods+for+a+Frozen-2FUnfrozen+String]
|
|
* - {Querying}[#class-String-label-Methods+for+Querying]
|
|
* - {Comparing}[#class-String-label-Methods+for+Comparing]
|
|
* - {Modifying a String}[#class-String-label-Methods+for+Modifying+a+String]
|
|
* - {Converting to New String}[#class-String-label-Methods+for+Converting+to+New+String]
|
|
* - {Converting to Non-String}[#class-String-label-Methods+for+Converting+to+Non--5CString]
|
|
* - {Iterating}[#class-String-label-Methods+for+Iterating]
|
|
*
|
|
* === Methods for Creating a \String
|
|
*
|
|
* - ::new:: Returns a new string.
|
|
* - ::try_convert:: Returns a new string created from a given object.
|
|
*
|
|
* === Methods for a Frozen/Unfrozen String
|
|
*
|
|
* - {#+string}[#method-i-2B-40]:: Returns a string that is not frozen:
|
|
* +self+, if not frozen; +self.dup+ otherwise.
|
|
* - {#-string}[#method-i-2D-40]:: Returns a string that is frozen:
|
|
* +self+, if already frozen; +self.freeze+ otherwise.
|
|
* - #freeze:: Freezes +self+, if not already frozen; returns +self+.
|
|
*
|
|
* === Methods for Querying
|
|
*
|
|
* _Counts_
|
|
*
|
|
* - #length, #size:: Returns the count of characters (not bytes).
|
|
* - #empty?:: Returns +true+ if +self.length+ is zero; +false+ otherwise.
|
|
* - #bytesize:: Returns the count of bytes.
|
|
* - #count:: Returns the count of substrings matching given strings.
|
|
*
|
|
* _Substrings_
|
|
*
|
|
* - {#=~}[#method-i-3D~]:: Returns the index of the first substring that matches a given Regexp or other object;
|
|
* returns +nil+ if no match is found.
|
|
* - #index:: Returns the index of the _first_ occurrence of a given substring;
|
|
* returns +nil+ if none found.
|
|
* - #rindex:: Returns the index of the _last_ occurrence of a given substring;
|
|
* returns +nil+ if none found.
|
|
* - #include?:: Returns +true+ if the string contains a given substring; +false+ otherwise.
|
|
* - #match:: Returns a MatchData object if the string matches a given Regexp; +nil+ otherwise.
|
|
* - #match?:: Returns +true+ if the string matches a given Regexp; +false+ otherwise.
|
|
* - #start_with?:: Returns +true+ if the string begins with any of the given substrings.
|
|
* - #end_with?:: Returns +true+ if the string ends with any of the given substrings.
|
|
*
|
|
* _Encodings_
|
|
*
|
|
* - #encoding:: Returns the Encoding object that represents the encoding of the string.
|
|
* - #unicode_normalized?:: Returns +true+ if the string is in Unicode normalized form; +false+ otherwise.
|
|
* - #valid_encoding?:: Returns +true+ if the string contains only characters that are valid
|
|
* for its encoding.
|
|
* - #ascii_only?:: Returns +true+ if the string has only ASCII characters; +false+ otherwise.
|
|
*
|
|
* _Other_
|
|
*
|
|
* - #sum:: Returns a basic checksum for the string: the sum of each byte.
|
|
* - #hash:: Returns the integer hash code.
|
|
*
|
|
* === Methods for Comparing
|
|
*
|
|
* - {#==, #===}[#method-i-3D-3D]:: Returns +true+ if a given other string has the same content as +self+.
|
|
* - #eql?:: Returns +true+ if the content is the same as the given other string.
|
|
* - {#<=>}[#method-i-3C-3D-3E]:: Returns -1, 0, or 1 as a given other string is smaller than, equal to, or larger than +self+.
|
|
* - #casecmp:: Ignoring case, returns -1, 0, or 1 as a given
|
|
* other string is smaller than, equal to, or larger than +self+.
|
|
* - #casecmp?:: Returns +true+ if the string is equal to a given string after Unicode case folding;
|
|
* +false+ otherwise.
|
|
*
|
|
* === Methods for Modifying a \String
|
|
*
|
|
* Each of these methods modifies +self+.
|
|
*
|
|
* _Insertion_
|
|
*
|
|
* - #insert:: Returns +self+ with a given string inserted at a given offset.
|
|
* - #<<:: Returns +self+ concatenated with a given string or integer.
|
|
*
|
|
* _Substitution_
|
|
*
|
|
* - #sub!:: Replaces the first substring that matches a given pattern with a given replacement string;
|
|
* returns +self+ if any changes, +nil+ otherwise.
|
|
* - #gsub!:: Replaces each substring that matches a given pattern with a given replacement string;
|
|
* returns +self+ if any changes, +nil+ otherwise.
|
|
* - #succ!, #next!:: Returns +self+ modified to become its own successor.
|
|
* - #replace:: Returns +self+ with its entire content replaced by a given string.
|
|
* - #reverse!:: Returns +self+ with its characters in reverse order.
|
|
* - #setbyte:: Sets the byte at a given integer offset to a given value; returns the argument.
|
|
* - #tr!:: Replaces specified characters in +self+ with specified replacement characters;
|
|
* returns +self+ if any changes, +nil+ otherwise.
|
|
* - #tr_s!:: Replaces specified characters in +self+ with specified replacement characters,
|
|
* removing duplicates from the substrings that were modified;
|
|
* returns +self+ if any changes, +nil+ otherwise.
|
|
*
|
|
* _Casing_
|
|
*
|
|
* - #capitalize!:: Upcases the initial character and downcases all others;
|
|
* returns +self+ if any changes, +nil+ otherwise.
|
|
* - #downcase!:: Downcases all characters; returns +self+ if any changes, +nil+ otherwise.
|
|
* - #upcase!:: Upcases all characters; returns +self+ if any changes, +nil+ otherwise.
|
|
* - #swapcase!:: Upcases each downcase character and downcases each upcase character;
|
|
* returns +self+ if any changes, +nil+ otherwise.
|
|
*
|
|
* _Encoding_
|
|
*
|
|
* - #encode!:: Returns +self+ with all characters transcoded from one given encoding into another.
|
|
* - #unicode_normalize!:: Unicode-normalizes +self+; returns +self+.
|
|
* - #scrub!:: Replaces each invalid byte with a given character; returns +self+.
|
|
* - #force_encoding:: Changes the encoding to a given encoding; returns +self+.
|
|
*
|
|
* _Deletion_
|
|
*
|
|
* - #clear:: Removes all content, so that +self+ is empty; returns +self+.
|
|
* - #slice!, #[]=:: Removes a substring determined by a given index, start/length, range, regexp, or substring.
|
|
* - #squeeze!:: Removes contiguous duplicate characters; returns +self+.
|
|
* - #delete!:: Removes characters as determined by the intersection of substring arguments.
|
|
* - #lstrip!:: Removes leading whitespace; returns +self+ if any changes, +nil+ otherwise.
|
|
* - #rstrip!:: Removes trailing whitespace; returns +self+ if any changes, +nil+ otherwise.
|
|
* - #strip!:: Removes leading and trailing whitespace; returns +self+ if any changes, +nil+ otherwise.
|
|
* - #chomp!:: Removes trailing record separator, if found; returns +self+ if any changes, +nil+ otherwise.
|
|
* - #chop!:: Removes trailing whitespace if found, otherwise removes the last character;
|
|
* returns +self+ if any changes, +nil+ otherwise.
|
|
*
|
|
* === Methods for Converting to New \String
|
|
*
|
|
* Each of these methods returns a new \String based on +self+,
|
|
* often just a modified copy of +self+.
|
|
*
|
|
* _Extension_
|
|
*
|
|
* - #*:: Returns the concatenation of multiple copies of +self+,
|
|
* - #+:: Returns the concatenation of +self+ and a given other string.
|
|
* - #center:: Returns a copy of +self+ centered between pad substring.
|
|
* - #concat:: Returns the concatenation of +self+ with given other strings.
|
|
* - #prepend:: Returns the concatenation of a given other string with +self+.
|
|
* - #ljust:: Returns a copy of +self+ of a given length, right-padded with a given other string.
|
|
* - #rjust:: Returns a copy of +self+ of a given length, left-padded with a given other string.
|
|
*
|
|
* _Encoding_
|
|
*
|
|
* - #b:: Returns a copy of +self+ with ASCII-8BIT encoding.
|
|
* - #scrub:: Returns a copy of +self+ with each invalid byte replaced with a given character.
|
|
* - #unicode_normalize:: Returns a copy of +self+ with each character Unicode-normalized.
|
|
* - #encode:: Returns a copy of +self+ with all characters transcoded from one given encoding into another.
|
|
*
|
|
* _Substitution_
|
|
*
|
|
* - #dump:: Returns a copy of +self with all non-printing characters replaced by \xHH notation
|
|
* and all special characters escaped.
|
|
* - #undump:: Returns a copy of +self with all <tt>\xNN</tt> notation replace by <tt>\uNNNN</tt> notation
|
|
* and all escaped characters unescaped.
|
|
* - #sub:: Returns a copy of +self+ with the first substring matching a given pattern
|
|
* replaced with a given replacement string;.
|
|
* - #gsub:: Returns a copy of +self+ with each substring that matches a given pattern
|
|
* replaced with a given replacement string.
|
|
* - #succ, #next:: Returns the string that is the successor to +self+.
|
|
* - #reverse:: Returns a copy of +self+ with its characters in reverse order.
|
|
* - #tr:: Returns a copy of +self+ with specified characters replaced with specified replacement characters.
|
|
* - #tr_s:: Returns a copy of +self+ with specified characters replaced with specified replacement characters,
|
|
* removing duplicates from the substrings that were modified.
|
|
* - #%:: Returns the string resulting from formatting a given object into +self+
|
|
*
|
|
* _Casing_
|
|
*
|
|
* - #capitalize:: Returns a copy of +self+ with the first character upcased
|
|
* and all other characters downcased.
|
|
* - #downcase:: Returns a copy of +self+ with all characters downcased.
|
|
* - #upcase:: Returns a copy of +self+ with all characters upcased.
|
|
* - #swapcase:: Returns a copy of +self+ with all upcase characters downcased
|
|
* and all downcase characters upcased.
|
|
*
|
|
* _Deletion_
|
|
*
|
|
* - #delete:: Returns a copy of +self+ with characters removed
|
|
* - #delete_prefix:: Returns a copy of +self+ with a given prefix removed.
|
|
* - #delete_suffix:: Returns a copy of +self+ with a given suffix removed.
|
|
* - #lstrip:: Returns a copy of +self+ with leading whitespace removed.
|
|
* - #rstrip:: Returns a copy of +self+ with trailing whitespace removed.
|
|
* - #strip:: Returns a copy of +self+ with leading and trailing whitespace removed.
|
|
* - #chomp:: Returns a copy of +self+ with a trailing record separator removed, if found.
|
|
* - #chop:: Returns a copy of +self+ with trailing whitespace or the last character removed.
|
|
* - #squeeze:: Returns a copy of +self+ with contiguous duplicate characters removed.
|
|
* - #[], #slice:: Returns a substring determined by a given index, start/length, or range, or string.
|
|
* - #byteslice:: Returns a substring determined by a given index, start/length, or range.
|
|
* - #chr:: Returns the first character.
|
|
*
|
|
* _Duplication_
|
|
*
|
|
* - #to_s, $to_str:: If +self+ is a subclass of \String, returns +self+ copied into a \String;
|
|
* otherwise, returns +self+.
|
|
*
|
|
* === Methods for Converting to Non-\String
|
|
*
|
|
* Each of these methods converts the contents of +self+ to a non-\String.
|
|
*
|
|
* <em>Characters, Bytes, and Clusters</em>
|
|
*
|
|
* - #bytes:: Returns an array of the bytes in +self+.
|
|
* - #chars:: Returns an array of the characters in +self+.
|
|
* - #codepoints:: Returns an array of the integer ordinals in +self+.
|
|
* - #getbyte:: Returns an integer byte as determined by a given index.
|
|
* - #grapheme_clusters:: Returns an array of the grapheme clusters in +self+.
|
|
*
|
|
* _Splitting_
|
|
*
|
|
* - #lines:: Returns an array of the lines in +self+, as determined by a given record separator.
|
|
* - #partition:: Returns a 3-element array determined by the first substring that matches
|
|
* a given substring or regexp,
|
|
* - #rpartition:: Returns a 3-element array determined by the last substring that matches
|
|
* a given substring or regexp,
|
|
* - #split:: Returns an array of substrings determined by a given delimiter -- regexp or string --
|
|
* or, if a block given, passes those substrings to the block.
|
|
*
|
|
* _Matching_
|
|
*
|
|
* - #scan:: Returns an array of substrings matching a given regexp or string, or,
|
|
* if a block given, passes each matching substring to the block.
|
|
* - #unpack:: Returns an array of substrings extracted from +self+ according to a given format.
|
|
* - #unpack1:: Returns the first substring extracted from +self+ according to a given format.
|
|
*
|
|
* _Numerics_
|
|
*
|
|
* - #hex:: Returns the integer value of the leading characters, interpreted as hexadecimal digits.
|
|
* - #oct:: Returns the integer value of the leading characters, interpreted as octal digits.
|
|
* - #ord:: Returns the integer ordinal of the first character in +self+.
|
|
* - #to_i:: Returns the integer value of leading characters, interpreted as an integer.
|
|
* - #to_f:: Returns the floating-point value of leading characters, interpreted as a floating-point number.
|
|
*
|
|
* <em>Strings and Symbols</em>
|
|
*
|
|
* - #inspect:: Returns copy of +self+, enclosed in double-quotes, with special characters escaped.
|
|
* - #to_sym, #intern:: Returns the symbol corresponding to +self+.
|
|
*
|
|
* === Methods for Iterating
|
|
*
|
|
* - #each_byte:: Calls the given block with each successive byte in +self+.
|
|
* - #each_char:: Calls the given block with each successive character in +self+.
|
|
* - #each_codepoint:: Calls the given block with each successive integer codepoint in +self+.
|
|
* - #each_grapheme_cluster:: Calls the given block with each successive grapheme cluster in +self+.
|
|
* - #each_line:: Calls the given block with each successive line in +self+,
|
|
* as determined by a given record separator.
|
|
* - #upto:: Calls the given block with each string value returned by successive calls to #succ.
|
|
*/
|
|
|
|
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_include_module(rb_cString, rb_mComparable);
|
|
rb_define_alloc_func(rb_cString, empty_str_alloc);
|
|
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, "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);
|
|
rb_define_method(rb_cString, "===", rb_str_equal, 1);
|
|
rb_define_method(rb_cString, "eql?", rb_str_eql, 1);
|
|
rb_define_method(rb_cString, "hash", rb_str_hash_m, 0);
|
|
rb_define_method(rb_cString, "casecmp", rb_str_casecmp, 1);
|
|
rb_define_method(rb_cString, "casecmp?", rb_str_casecmp_p, 1);
|
|
rb_define_method(rb_cString, "+", rb_str_plus, 1);
|
|
rb_define_method(rb_cString, "*", rb_str_times, 1);
|
|
rb_define_method(rb_cString, "%", rb_str_format_m, 1);
|
|
rb_define_method(rb_cString, "[]", rb_str_aref_m, -1);
|
|
rb_define_method(rb_cString, "[]=", rb_str_aset_m, -1);
|
|
rb_define_method(rb_cString, "insert", rb_str_insert, 2);
|
|
rb_define_method(rb_cString, "length", rb_str_length, 0);
|
|
rb_define_method(rb_cString, "size", rb_str_length, 0);
|
|
rb_define_method(rb_cString, "bytesize", rb_str_bytesize, 0);
|
|
rb_define_method(rb_cString, "empty?", rb_str_empty, 0);
|
|
rb_define_method(rb_cString, "=~", rb_str_match, 1);
|
|
rb_define_method(rb_cString, "match", rb_str_match_m, -1);
|
|
rb_define_method(rb_cString, "match?", rb_str_match_m_p, -1);
|
|
rb_define_method(rb_cString, "succ", rb_str_succ, 0);
|
|
rb_define_method(rb_cString, "succ!", rb_str_succ_bang, 0);
|
|
rb_define_method(rb_cString, "next", rb_str_succ, 0);
|
|
rb_define_method(rb_cString, "next!", rb_str_succ_bang, 0);
|
|
rb_define_method(rb_cString, "upto", rb_str_upto, -1);
|
|
rb_define_method(rb_cString, "index", rb_str_index_m, -1);
|
|
rb_define_method(rb_cString, "rindex", rb_str_rindex_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);
|
|
rb_define_method(rb_cString, "setbyte", rb_str_setbyte, 2);
|
|
rb_define_method(rb_cString, "byteslice", rb_str_byteslice, -1);
|
|
rb_define_method(rb_cString, "scrub", str_scrub, -1);
|
|
rb_define_method(rb_cString, "scrub!", str_scrub_bang, -1);
|
|
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, "to_i", rb_str_to_i, -1);
|
|
rb_define_method(rb_cString, "to_f", rb_str_to_f, 0);
|
|
rb_define_method(rb_cString, "to_s", rb_str_to_s, 0);
|
|
rb_define_method(rb_cString, "to_str", rb_str_to_s, 0);
|
|
rb_define_method(rb_cString, "inspect", rb_str_inspect, 0);
|
|
rb_define_method(rb_cString, "dump", rb_str_dump, 0);
|
|
rb_define_method(rb_cString, "undump", str_undump, 0);
|
|
|
|
sym_ascii = ID2SYM(rb_intern_const("ascii"));
|
|
sym_turkic = ID2SYM(rb_intern_const("turkic"));
|
|
sym_lithuanian = ID2SYM(rb_intern_const("lithuanian"));
|
|
sym_fold = ID2SYM(rb_intern_const("fold"));
|
|
|
|
rb_define_method(rb_cString, "upcase", rb_str_upcase, -1);
|
|
rb_define_method(rb_cString, "downcase", rb_str_downcase, -1);
|
|
rb_define_method(rb_cString, "capitalize", rb_str_capitalize, -1);
|
|
rb_define_method(rb_cString, "swapcase", rb_str_swapcase, -1);
|
|
|
|
rb_define_method(rb_cString, "upcase!", rb_str_upcase_bang, -1);
|
|
rb_define_method(rb_cString, "downcase!", rb_str_downcase_bang, -1);
|
|
rb_define_method(rb_cString, "capitalize!", rb_str_capitalize_bang, -1);
|
|
rb_define_method(rb_cString, "swapcase!", rb_str_swapcase_bang, -1);
|
|
|
|
rb_define_method(rb_cString, "hex", rb_str_hex, 0);
|
|
rb_define_method(rb_cString, "oct", rb_str_oct, 0);
|
|
rb_define_method(rb_cString, "split", rb_str_split_m, -1);
|
|
rb_define_method(rb_cString, "lines", rb_str_lines, -1);
|
|
rb_define_method(rb_cString, "bytes", rb_str_bytes, 0);
|
|
rb_define_method(rb_cString, "chars", rb_str_chars, 0);
|
|
rb_define_method(rb_cString, "codepoints", rb_str_codepoints, 0);
|
|
rb_define_method(rb_cString, "grapheme_clusters", rb_str_grapheme_clusters, 0);
|
|
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, "<<", 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);
|
|
rb_define_method(rb_cString, "intern", rb_str_intern, 0); /* in symbol.c */
|
|
rb_define_method(rb_cString, "to_sym", rb_str_intern, 0); /* in symbol.c */
|
|
rb_define_method(rb_cString, "ord", rb_str_ord, 0);
|
|
|
|
rb_define_method(rb_cString, "include?", rb_str_include, 1);
|
|
rb_define_method(rb_cString, "start_with?", rb_str_start_with, -1);
|
|
rb_define_method(rb_cString, "end_with?", rb_str_end_with, -1);
|
|
|
|
rb_define_method(rb_cString, "scan", rb_str_scan, 1);
|
|
|
|
rb_define_method(rb_cString, "ljust", rb_str_ljust, -1);
|
|
rb_define_method(rb_cString, "rjust", rb_str_rjust, -1);
|
|
rb_define_method(rb_cString, "center", rb_str_center, -1);
|
|
|
|
rb_define_method(rb_cString, "sub", rb_str_sub, -1);
|
|
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, "delete_prefix", rb_str_delete_prefix, 1);
|
|
rb_define_method(rb_cString, "delete_suffix", rb_str_delete_suffix, 1);
|
|
|
|
rb_define_method(rb_cString, "sub!", rb_str_sub_bang, -1);
|
|
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, "delete_prefix!", rb_str_delete_prefix_bang, 1);
|
|
rb_define_method(rb_cString, "delete_suffix!", rb_str_delete_suffix_bang, 1);
|
|
|
|
rb_define_method(rb_cString, "tr", rb_str_tr, 2);
|
|
rb_define_method(rb_cString, "tr_s", rb_str_tr_s, 2);
|
|
rb_define_method(rb_cString, "delete", rb_str_delete, -1);
|
|
rb_define_method(rb_cString, "squeeze", rb_str_squeeze, -1);
|
|
rb_define_method(rb_cString, "count", rb_str_count, -1);
|
|
|
|
rb_define_method(rb_cString, "tr!", rb_str_tr_bang, 2);
|
|
rb_define_method(rb_cString, "tr_s!", rb_str_tr_s_bang, 2);
|
|
rb_define_method(rb_cString, "delete!", rb_str_delete_bang, -1);
|
|
rb_define_method(rb_cString, "squeeze!", rb_str_squeeze_bang, -1);
|
|
|
|
rb_define_method(rb_cString, "each_line", rb_str_each_line, -1);
|
|
rb_define_method(rb_cString, "each_byte", rb_str_each_byte, 0);
|
|
rb_define_method(rb_cString, "each_char", rb_str_each_char, 0);
|
|
rb_define_method(rb_cString, "each_codepoint", rb_str_each_codepoint, 0);
|
|
rb_define_method(rb_cString, "each_grapheme_cluster", rb_str_each_grapheme_cluster, 0);
|
|
|
|
rb_define_method(rb_cString, "sum", rb_str_sum, -1);
|
|
|
|
rb_define_method(rb_cString, "slice", rb_str_aref_m, -1);
|
|
rb_define_method(rb_cString, "slice!", rb_str_slice_bang, -1);
|
|
|
|
rb_define_method(rb_cString, "partition", rb_str_partition, 1);
|
|
rb_define_method(rb_cString, "rpartition", rb_str_rpartition, 1);
|
|
|
|
rb_define_method(rb_cString, "encoding", rb_obj_encoding, 0); /* in encoding.c */
|
|
rb_define_method(rb_cString, "force_encoding", rb_str_force_encoding, 1);
|
|
rb_define_method(rb_cString, "b", rb_str_b, 0);
|
|
rb_define_method(rb_cString, "valid_encoding?", rb_str_valid_encoding_p, 0);
|
|
rb_define_method(rb_cString, "ascii_only?", rb_str_is_ascii_only_p, 0);
|
|
|
|
/* define UnicodeNormalize module here so that we don't have to look it up */
|
|
mUnicodeNormalize = rb_define_module("UnicodeNormalize");
|
|
id_normalize = rb_intern_const("normalize");
|
|
id_normalized_p = rb_intern_const("normalized?");
|
|
|
|
rb_define_method(rb_cString, "unicode_normalize", rb_str_unicode_normalize, -1);
|
|
rb_define_method(rb_cString, "unicode_normalize!", rb_str_unicode_normalize_bang, -1);
|
|
rb_define_method(rb_cString, "unicode_normalized?", rb_str_unicode_normalized_p, -1);
|
|
|
|
rb_fs = Qnil;
|
|
rb_define_hooked_variable("$;", &rb_fs, 0, rb_fs_setter);
|
|
rb_define_hooked_variable("$-F", &rb_fs, 0, rb_fs_setter);
|
|
rb_gc_register_address(&rb_fs);
|
|
|
|
rb_cSymbol = rb_define_class("Symbol", rb_cObject);
|
|
rb_include_module(rb_cSymbol, rb_mComparable);
|
|
rb_undef_alloc_func(rb_cSymbol);
|
|
rb_undef_method(CLASS_OF(rb_cSymbol), "new");
|
|
rb_define_singleton_method(rb_cSymbol, "all_symbols", sym_all_symbols, 0);
|
|
|
|
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);
|
|
rb_define_method(rb_cSymbol, "intern", sym_to_sym, 0);
|
|
rb_define_method(rb_cSymbol, "to_sym", sym_to_sym, 0);
|
|
rb_define_method(rb_cSymbol, "to_proc", rb_sym_to_proc, 0);
|
|
rb_define_method(rb_cSymbol, "succ", sym_succ, 0);
|
|
rb_define_method(rb_cSymbol, "next", sym_succ, 0);
|
|
|
|
rb_define_method(rb_cSymbol, "<=>", sym_cmp, 1);
|
|
rb_define_method(rb_cSymbol, "casecmp", sym_casecmp, 1);
|
|
rb_define_method(rb_cSymbol, "casecmp?", sym_casecmp_p, 1);
|
|
rb_define_method(rb_cSymbol, "=~", sym_match, 1);
|
|
|
|
rb_define_method(rb_cSymbol, "[]", sym_aref, -1);
|
|
rb_define_method(rb_cSymbol, "slice", sym_aref, -1);
|
|
rb_define_method(rb_cSymbol, "length", sym_length, 0);
|
|
rb_define_method(rb_cSymbol, "size", sym_length, 0);
|
|
rb_define_method(rb_cSymbol, "empty?", sym_empty, 0);
|
|
rb_define_method(rb_cSymbol, "match", sym_match_m, -1);
|
|
rb_define_method(rb_cSymbol, "match?", sym_match_m_p, -1);
|
|
|
|
rb_define_method(rb_cSymbol, "upcase", sym_upcase, -1);
|
|
rb_define_method(rb_cSymbol, "downcase", sym_downcase, -1);
|
|
rb_define_method(rb_cSymbol, "capitalize", sym_capitalize, -1);
|
|
rb_define_method(rb_cSymbol, "swapcase", sym_swapcase, -1);
|
|
|
|
rb_define_method(rb_cSymbol, "start_with?", sym_start_with, -1);
|
|
rb_define_method(rb_cSymbol, "end_with?", sym_end_with, -1);
|
|
|
|
rb_define_method(rb_cSymbol, "encoding", sym_encoding, 0);
|
|
}
|