/********************************************************************** 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/ruby.h" #include "ruby/re.h" #include "ruby/encoding.h" #define BEG(no) regs->beg[no] #define END(no) regs->end[no] #include #include #ifdef HAVE_UNISTD_H #include #endif VALUE rb_cString; VALUE rb_cSymbol; #define STR_TMPLOCK FL_USER7 #define STR_NOEMBED FL_USER1 #define STR_SHARED FL_USER2 /* = ELTS_SHARED */ #define STR_ASSOC FL_USER3 #define STR_SHARED_P(s) FL_ALL(s, STR_NOEMBED|ELTS_SHARED) #define STR_ASSOC_P(s) FL_ALL(s, STR_NOEMBED|STR_ASSOC) #define STR_NOCAPA (STR_NOEMBED|ELTS_SHARED|STR_ASSOC) #define STR_NOCAPA_P(s) (FL_TEST(s,STR_NOEMBED) && FL_ANY(s,ELTS_SHARED|STR_ASSOC)) #define STR_UNSET_NOCAPA(s) do {\ if (FL_TEST(s,STR_NOEMBED)) FL_UNSET(s,(ELTS_SHARED|STR_ASSOC));\ } while (0) #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) #define STR_EMBED_P(str) (!FL_TEST(str, STR_NOEMBED)) #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 RESIZE_CAPA(str,capacity) do {\ if (STR_EMBED_P(str)) {\ if ((capacity) > RSTRING_EMBED_LEN_MAX) {\ char *tmp = ALLOC_N(char, capacity+1);\ memcpy(tmp, RSTRING_PTR(str), RSTRING_LEN(str));\ RSTRING(str)->as.heap.ptr = tmp;\ RSTRING(str)->as.heap.len = RSTRING_LEN(str);\ STR_SET_NOEMBED(str);\ RSTRING(str)->as.heap.aux.capa = (capacity);\ }\ }\ else {\ REALLOC_N(RSTRING(str)->as.heap.ptr, char, (capacity)+1);\ if (!STR_NOCAPA_P(str))\ RSTRING(str)->as.heap.aux.capa = (capacity);\ }\ } while (0) #define is_ascii_string(str) (rb_enc_str_coderange(str) == ENC_CODERANGE_7BIT) #define is_broken_string(str) (rb_enc_str_coderange(str) == ENC_CODERANGE_BROKEN) #define STR_ENC_GET(str) rb_enc_from_index(ENCODING_GET(str)) static int single_byte_optimizable(VALUE str) { rb_encoding *enc = STR_ENC_GET(str); if (rb_enc_mbmaxlen(enc) == 1) return 1; /* Conservative. It may be ENC_CODERANGE_UNKNOWN. */ if (ENC_CODERANGE(str) == ENC_CODERANGE_7BIT) 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) { #if ULONG_MAX == 18446744073709551615UL # define NONASCII_MASK 0x8080808080808080UL #elif ULONG_MAX == 4294967295UL # define NONASCII_MASK 0x80808080UL #endif #ifdef NONASCII_MASK if (sizeof(long) * 2 < e - p) { const unsigned long *s, *t; const VALUE lowbits = sizeof(unsigned long) - 1; s = (const unsigned long*)(~lowbits & ((VALUE)p + lowbits)); while (p < (const char *)s) { if (!ISASCII(*p)) return p; p++; } t = (const unsigned long*)(~lowbits & (VALUE)e); while (s < t) { if (*s & NONASCII_MASK) { t = s; break; } s++; } p = (const char *)t; } #endif while (p < e) { if (!ISASCII(*p)) return p; p++; } 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) == 0) { /* 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; } 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); if (p < e) { p = search_nonascii(p, e); if (!p) { return ENC_CODERANGE_VALID; } } } if (e < p) { return ENC_CODERANGE_BROKEN; } return ENC_CODERANGE_VALID; } 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); } if (e < p) { return ENC_CODERANGE_BROKEN; } return ENC_CODERANGE_VALID; } void rb_enc_str_copy(VALUE dest, VALUE src) { rb_enc_copy(dest, src); if (RSTRING_PTR(dest) == RSTRING_PTR(src) && RSTRING_LEN(dest) == RSTRING_LEN(src)) { ENC_CODERANGE_SET(dest, ENC_CODERANGE(src)); } } void rb_enc_cr_str_copy(VALUE dest, VALUE src) { rb_enc_copy(dest, src); if (!is_broken_string(src)) ENC_CODERANGE_SET(dest, ENC_CODERANGE(src)); } void rb_enc_cr_str_exact_copy(VALUE dest, VALUE src) { rb_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) { rb_encoding *enc = STR_ENC_GET(str); 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 Qfalse; else if (rb_enc_str_coderange(str) == ENC_CODERANGE_7BIT) return Qtrue; return Qfalse; } 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 inline void str_frozen_check(VALUE s) { if (OBJ_FROZEN(s)) { rb_raise(rb_eRuntimeError, "string frozen"); } } static VALUE str_alloc(VALUE klass) { NEWOBJ(str, struct RString); OBJSETUP(str, klass, T_STRING); if (klass == rb_cSymbol) { /* need to be registered in table */ RBASIC(str)->klass = rb_cString; } str->as.heap.ptr = 0; str->as.heap.len = 0; str->as.heap.aux.capa = 0; return (VALUE)str; } static VALUE str_new(VALUE klass, const char *ptr, long len) { VALUE str; if (len < 0) { rb_raise(rb_eArgError, "negative string size (or size too big)"); } str = str_alloc(klass); if (len > RSTRING_EMBED_LEN_MAX) { RSTRING(str)->as.heap.aux.capa = len; RSTRING(str)->as.heap.ptr = ALLOC_N(char,len+1); STR_SET_NOEMBED(str); } if (ptr) { memcpy(RSTRING_PTR(str), ptr, len); } if (len == 0) { ENCODING_CODERANGE_SET(str, rb_usascii_encindex(), ENC_CODERANGE_7BIT); } STR_SET_LEN(str, len); RSTRING_PTR(str)[len] = '\0'; return str; } 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 = str_new(rb_cString, ptr, len); ENCODING_CODERANGE_SET(str, rb_usascii_encindex(), ENC_CODERANGE_7BIT); return str; } VALUE rb_enc_str_new(const char *ptr, long len, rb_encoding *enc) { VALUE str = str_new(rb_cString, ptr, len); rb_enc_associate(str, enc); return str; } VALUE rb_str_new2(const char *ptr) { if (!ptr) { rb_raise(rb_eArgError, "NULL pointer given"); } return rb_str_new(ptr, strlen(ptr)); } VALUE rb_usascii_str_new2(const char *ptr) { if (!ptr) { rb_raise(rb_eArgError, "NULL pointer given"); } return rb_usascii_str_new(ptr, strlen(ptr)); } VALUE rb_tainted_str_new(const char *ptr, long len) { VALUE str = rb_str_new(ptr, len); OBJ_TAINT(str); return str; } VALUE rb_tainted_str_new2(const char *ptr) { VALUE str = rb_str_new2(ptr); OBJ_TAINT(str); return str; } static VALUE str_replace_shared(VALUE str2, VALUE str) { if (RSTRING_LEN(str) <= RSTRING_EMBED_LEN_MAX) { STR_SET_EMBED(str2); memcpy(RSTRING_PTR(str2), RSTRING_PTR(str), RSTRING_LEN(str)+1); STR_SET_EMBED_LEN(str2, RSTRING_LEN(str)); } else { FL_SET(str2, STR_NOEMBED); RSTRING(str2)->as.heap.len = RSTRING_LEN(str); RSTRING(str2)->as.heap.ptr = RSTRING_PTR(str); RSTRING(str2)->as.heap.aux.shared = str; FL_SET(str2, ELTS_SHARED); } 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); } static VALUE str_new3(VALUE klass, VALUE str) { return str_new_shared(klass, str); } VALUE rb_str_new3(VALUE str) { VALUE str2 = str_new3(rb_obj_class(str), str); OBJ_INFECT(str2, str); return str2; } static VALUE str_new4(VALUE klass, VALUE str) { VALUE str2; str2 = str_alloc(klass); STR_SET_NOEMBED(str2); RSTRING(str2)->as.heap.len = RSTRING_LEN(str); RSTRING(str2)->as.heap.ptr = RSTRING_PTR(str); if (STR_SHARED_P(str)) { FL_SET(str2, ELTS_SHARED); RSTRING(str2)->as.heap.aux.shared = RSTRING(str)->as.heap.aux.shared; } else { FL_SET(str, ELTS_SHARED); RSTRING(str)->as.heap.aux.shared = str2; } rb_enc_cr_str_exact_copy(str2, str); OBJ_INFECT(str2, str); return str2; } VALUE rb_str_new4(VALUE orig) { VALUE klass, str; if (OBJ_FROZEN(orig)) return orig; klass = rb_obj_class(orig); if (STR_SHARED_P(orig) && (str = RSTRING(orig)->as.heap.aux.shared)) { long ofs; ofs = RSTRING_LEN(str) - RSTRING_LEN(orig); if ((ofs > 0) || (klass != RBASIC(str)->klass) || (!OBJ_TAINTED(str) && OBJ_TAINTED(orig))) { str = str_new3(klass, str); RSTRING(str)->as.heap.ptr += ofs; RSTRING(str)->as.heap.len -= ofs; } rb_enc_cr_str_exact_copy(str, orig); OBJ_INFECT(str, orig); } else if (STR_EMBED_P(orig)) { str = str_new(klass, RSTRING_PTR(orig), RSTRING_LEN(orig)); rb_enc_cr_str_exact_copy(str, orig); OBJ_INFECT(str, orig); } else if (STR_ASSOC_P(orig)) { VALUE assoc = RSTRING(orig)->as.heap.aux.shared; FL_UNSET(orig, STR_ASSOC); str = str_new4(klass, orig); FL_SET(str, STR_ASSOC); RSTRING(str)->as.heap.aux.shared = assoc; } else { str = str_new4(klass, orig); } OBJ_FREEZE(str); return str; } VALUE rb_str_new5(VALUE obj, const char *ptr, long len) { return str_new(rb_obj_class(obj), ptr, len); } #define STR_BUF_MIN_SIZE 128 VALUE rb_str_buf_new(long capa) { VALUE str = str_alloc(rb_cString); 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, capa+1); RSTRING(str)->as.heap.ptr[0] = '\0'; return str; } VALUE rb_str_buf_new2(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 (!STR_EMBED_P(str) && !STR_SHARED_P(str)) { xfree(RSTRING(str)->as.heap.ptr); } } VALUE rb_str_to_str(VALUE str) { return rb_convert_type(str, T_STRING, "String", "to_str"); } void rb_str_shared_replace(VALUE str, VALUE str2) { rb_encoding *enc; int cr; if (str == str2) return; enc = STR_ENC_GET(str2); cr = ENC_CODERANGE(str2); rb_str_modify(str); if (OBJ_TAINTED(str2)) OBJ_TAINT(str); if (RSTRING_LEN(str2) <= RSTRING_EMBED_LEN_MAX) { STR_SET_EMBED(str); memcpy(RSTRING_PTR(str), RSTRING_PTR(str2), RSTRING_LEN(str2)+1); STR_SET_EMBED_LEN(str, RSTRING_LEN(str2)); rb_enc_associate(str, enc); ENC_CODERANGE_SET(str, cr); return; } if (!STR_SHARED_P(str) && !STR_EMBED_P(str)) { free(RSTRING_PTR(str)); } STR_SET_NOEMBED(str); STR_UNSET_NOCAPA(str); RSTRING(str)->as.heap.ptr = RSTRING_PTR(str2); RSTRING(str)->as.heap.len = RSTRING_LEN(str2); if (STR_NOCAPA_P(str2)) { FL_SET(str, RBASIC(str2)->flags & STR_NOCAPA); RSTRING(str)->as.heap.aux.shared = RSTRING(str2)->as.heap.aux.shared; } else { RSTRING(str)->as.heap.aux.capa = RSTRING(str2)->as.heap.aux.capa; } RSTRING(str2)->as.heap.ptr = 0; /* abandon str2 */ RSTRING(str2)->as.heap.len = 0; RSTRING(str2)->as.heap.aux.capa = 0; STR_UNSET_NOCAPA(str2); rb_enc_associate(str, enc); ENC_CODERANGE_SET(str, cr); } static ID id_to_s; VALUE rb_obj_as_string(VALUE obj) { VALUE str; if (TYPE(obj) == T_STRING) { return obj; } str = rb_funcall(obj, id_to_s, 0); if (TYPE(str) != T_STRING) return rb_any_to_s(obj); if (OBJ_TAINTED(obj)) OBJ_TAINT(str); return str; } static VALUE rb_str_replace(VALUE, VALUE); VALUE rb_str_dup(VALUE str) { VALUE dup = str_alloc(rb_obj_class(str)); rb_str_replace(dup, str); return dup; } /* * call-seq: * String.new(str="") => new_str * * Returns a new string object containing a copy of str. */ static VALUE rb_str_init(int argc, VALUE *argv, VALUE str) { VALUE orig; if (rb_scan_args(argc, argv, "01", &orig) == 1) rb_str_replace(str, orig); return str; } long rb_enc_strlen(const char *p, const char *e, rb_encoding *enc) { long c; const char *q; if (rb_enc_mbmaxlen(enc) == rb_enc_mbminlen(enc)) { return (e - p) / 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) return c + (e - p); c += q - p; p = q; } p += rb_enc_mbclen(p, e, enc); c++; } return c; } for (c=0; p>= 7; d += (d>>8); d += (d>>16); #if NONASCII_MASK == 0x8080808080808080UL d = d + (d>>32); #endif len += (long)(d&0xF); s++; } p = (const char *)t; } for (; p integer * str.size => integer * * Returns the character length of str. */ VALUE rb_str_length(VALUE str) { int len; len = str_strlen(str, STR_ENC_GET(str)); return INT2NUM(len); } /* * call-seq: * str.bytesize => integer * * Returns the length of str in bytes. */ static VALUE rb_str_bytesize(VALUE str) { return INT2NUM(RSTRING_LEN(str)); } /* * call-seq: * str.empty? => true or false * * Returns true if str has a length of zero. * * "hello".empty? #=> false * "".empty? #=> true */ static VALUE rb_str_empty(VALUE str) { if (RSTRING_LEN(str) == 0) return Qtrue; return Qfalse; } /* * call-seq: * str + other_str => new_str * * Concatenation---Returns a new String containing * other_str concatenated to str. * * "Hello from " + self.to_s #=> "Hello from main" */ VALUE rb_str_plus(VALUE str1, VALUE str2) { VALUE str3; rb_encoding *enc; StringValue(str2); enc = rb_enc_check(str1, str2); str3 = rb_str_new(0, RSTRING_LEN(str1)+RSTRING_LEN(str2)); memcpy(RSTRING_PTR(str3), RSTRING_PTR(str1), RSTRING_LEN(str1)); memcpy(RSTRING_PTR(str3) + RSTRING_LEN(str1), RSTRING_PTR(str2), RSTRING_LEN(str2)); RSTRING_PTR(str3)[RSTRING_LEN(str3)] = '\0'; if (OBJ_TAINTED(str1) || OBJ_TAINTED(str2)) OBJ_TAINT(str3); ENCODING_CODERANGE_SET(str3, rb_enc_to_index(enc), ENC_CODERANGE_AND(ENC_CODERANGE(str1), ENC_CODERANGE(str2))); return str3; } /* * call-seq: * str * integer => new_str * * Copy---Returns a new String containing integer copies of * the receiver. * * "Ho! " * 3 #=> "Ho! Ho! Ho! " */ VALUE rb_str_times(VALUE str, VALUE times) { VALUE str2; long n, len; int cr; len = NUM2LONG(times); if (len < 0) { rb_raise(rb_eArgError, "negative argument"); } if (len && LONG_MAX/len < RSTRING_LEN(str)) { rb_raise(rb_eArgError, "argument too big"); } str2 = rb_str_new5(str, 0, len *= RSTRING_LEN(str)); if (len) { n = RSTRING_LEN(str); memcpy(RSTRING_PTR(str2), RSTRING_PTR(str), n); while (n <= len/2) { memcpy(RSTRING_PTR(str2) + n, RSTRING_PTR(str2), n); n *= 2; } memcpy(RSTRING_PTR(str2) + n, RSTRING_PTR(str2), len-n); cr = ENC_CODERANGE(str); if (cr == ENC_CODERANGE_BROKEN) cr = ENC_CODERANGE_UNKNOWN; } else { cr = ENC_CODERANGE_7BIT; } RSTRING_PTR(str2)[RSTRING_LEN(str2)] = '\0'; OBJ_INFECT(str2, str); ENCODING_CODERANGE_SET(str2, rb_enc_get_index(str), cr); return str2; } /* * call-seq: * str % arg => new_str * * Format---Uses str as a format specification, and returns the result * of applying it to arg. If the format specification contains more than * one substitution, then arg must be an Array containing * the values to be substituted. See Kernel::sprintf for details * of the format string. * * "%05d" % 123 #=> "00123" * "%-5s: %08x" % [ "ID", self.id ] #=> "ID : 200e14d6" */ 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_LEN(tmp), RARRAY_PTR(tmp), str); } return rb_str_format(1, &arg, str); } static void str_modifiable(VALUE str) { if (FL_TEST(str, STR_TMPLOCK)) { rb_raise(rb_eRuntimeError, "can't modify string; temporarily locked"); } if (OBJ_FROZEN(str)) rb_error_frozen("string"); if (!OBJ_TAINTED(str) && rb_safe_level() >= 4) rb_raise(rb_eSecurityError, "Insecure: can't modify string"); } static int str_independent(VALUE str) { str_modifiable(str); if (!STR_SHARED_P(str)) return 1; if (STR_EMBED_P(str)) return 1; return 0; } static void str_make_independent(VALUE str) { char *ptr; long len = RSTRING_LEN(str); ptr = ALLOC_N(char, len+1); if (RSTRING_PTR(str)) { memcpy(ptr, RSTRING_PTR(str), len); } STR_SET_NOEMBED(str); ptr[len] = 0; RSTRING(str)->as.heap.ptr = ptr; RSTRING(str)->as.heap.len = len; RSTRING(str)->as.heap.aux.capa = len; STR_UNSET_NOCAPA(str); } void rb_str_modify(VALUE str) { if (!str_independent(str)) str_make_independent(str); ENC_CODERANGE_CLEAR(str); } void rb_str_associate(VALUE str, VALUE add) { /* sanity check */ if (OBJ_FROZEN(str)) rb_error_frozen("string"); if (STR_ASSOC_P(str)) { /* already associated */ rb_ary_concat(RSTRING(str)->as.heap.aux.shared, add); } else { if (STR_SHARED_P(str)) { VALUE assoc = RSTRING(str)->as.heap.aux.shared; str_make_independent(str); if (STR_ASSOC_P(assoc)) { assoc = RSTRING(assoc)->as.heap.aux.shared; rb_ary_concat(assoc, add); add = assoc; } } else if (STR_EMBED_P(str)) { str_make_independent(str); } else if (RSTRING(str)->as.heap.aux.capa != RSTRING_LEN(str)) { RESIZE_CAPA(str, RSTRING_LEN(str)); } FL_SET(str, STR_ASSOC); RBASIC(add)->klass = 0; RSTRING(str)->as.heap.aux.shared = add; } } VALUE rb_str_associated(VALUE str) { if (STR_SHARED_P(str)) str = RSTRING(str)->as.heap.aux.shared; if (STR_ASSOC_P(str)) { return RSTRING(str)->as.heap.aux.shared; } return Qfalse; } VALUE rb_string_value(volatile VALUE *ptr) { VALUE s = *ptr; if (TYPE(s) != T_STRING) { s = rb_str_to_str(s); *ptr = s; } return s; } char * rb_string_value_ptr(volatile VALUE *ptr) { return RSTRING_PTR(rb_string_value(ptr)); } char * rb_string_value_cstr(volatile VALUE *ptr) { VALUE str = rb_string_value(ptr); char *s = RSTRING_PTR(str); if (!s || RSTRING_LEN(str) != strlen(s)) { rb_raise(rb_eArgError, "string contains null byte"); } return s; } VALUE rb_check_string_type(VALUE str) { str = rb_check_convert_type(str, T_STRING, "String", "to_str"); return str; } /* * call-seq: * String.try_convert(obj) -> string or nil * * Try to convert obj into a String, using to_str method. * Returns converted regexp or nil if obj cannot be converted * for any reason. * * String.try_convert("str") # => str * String.try_convert(/re/) # => nil */ static VALUE rb_str_s_try_convert(VALUE dummy, VALUE str) { return rb_check_string_type(str); } char* rb_enc_nth(const char *p, const char *e, int nth, rb_encoding *enc) { 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) return (char *)e; if (ISASCII(*p)) { p2 = search_nonascii(p, e2); if (!p2) return (char *)e2; nth -= p2 - p; p = p2; } n = rb_enc_mbclen(p, e, enc); p += n; nth--; } if (nth != 0) return (char *)e; return (char *)p; } else { while (p e) p = e; return (char*)p; } static char* str_nth(const char *p, const char *e, int nth, rb_encoding *enc, int singlebyte) { if (singlebyte) p += nth; else { p = rb_enc_nth(p, e, nth, enc); } if (!p) return 0; if (p > e) p = e; return (char *)p; } static int str_offset(const char *p, const char *e, int nth, rb_encoding *enc, int singlebyte) { const char *pp = str_nth(p, e, nth, enc, singlebyte); if (!pp) return e - p; return pp - p; } #ifdef NONASCII_MASK static char * str_utf8_nth(const char *p, const char *e, int nth) { if (sizeof(long) * 2 < nth) { const unsigned long *s, *t; const VALUE lowbits = sizeof(unsigned long) - 1; s = (const unsigned long*)(~lowbits & ((VALUE)p + lowbits)); t = (const unsigned long*)(~lowbits & (VALUE)e); for (; p<(const char *)s && 0>= 7; d += (d>>8); d += (d>>16); #if NONASCII_MASK == 0x8080808080808080UL d += (d>>32); #endif nth -= (long)(d&0xF); if (nth < 8) { t = s; break; } } p = (char *)t; } if (0 < nth) { while (p < e) { if (((*p)&0xC0) != 0x80) { nth--; if (nth < 0) break; } p++; } } return (char *)p; } static int str_utf8_offset(const char *p, const char *e, int nth) { const char *pp = str_utf8_nth(p, e, nth); if (!pp) return e - p; return pp - p; } #endif static long str_sublen(VALUE str, long pos, rb_encoding *enc) { if (rb_enc_mbmaxlen(enc) == 1 || pos < 0) return pos; else { char *p = RSTRING_PTR(str); return rb_enc_strlen(p, p + pos, enc); } } int rb_str_sublen(VALUE str, int len) { return str_sublen(str, len, STR_ENC_GET(str)); } VALUE rb_str_subseq(VALUE str, long beg, long len) { VALUE str2 = rb_str_new5(str, RSTRING_PTR(str)+beg, len); rb_enc_cr_str_copy(str2, str); OBJ_INFECT(str2, str); return str2; } VALUE rb_str_substr(VALUE str, long beg, long len) { rb_encoding *enc = STR_ENC_GET(str); VALUE str2; char *p, *s = RSTRING_PTR(str), *e = s + RSTRING_LEN(str); int singlebyte = single_byte_optimizable(str); if (len < 0) return Qnil; if (!RSTRING_LEN(str)) { len = 0; } 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, enc)) != 0); p = e; if (!p) return Qnil; while (len-- > 0 && (p = rb_enc_prev_char(s, p, enc)) != 0); if (!p) return Qnil; len = e - p; goto sub; } else { beg += str_strlen(str, enc); if (beg < 0) return Qnil; } } else if (beg > 0 && beg > str_strlen(str, enc)) { return Qnil; } if (len == 0) { p = 0; } #ifdef NONASCII_MASK else if (ENC_CODERANGE(str) == ENC_CODERANGE_VALID && enc == rb_utf8_encoding()) { p = str_utf8_nth(s, e, beg); len = str_utf8_offset(p, e, len); } #endif else if ((p = str_nth(s, e, beg, enc, singlebyte)) == e) { len = 0; } else if (rb_enc_mbmaxlen(enc) == rb_enc_mbminlen(enc)) { long rest = (e - p) / rb_enc_mbmaxlen(enc); if (len > rest) len = rest; else len *= rb_enc_mbmaxlen(enc); } else { len = str_offset(p, e, len, enc, singlebyte); } sub: if (len > RSTRING_EMBED_LEN_MAX && beg + len == RSTRING_LEN(str)) { str2 = rb_str_new4(str); str2 = str_new3(rb_obj_class(str2), str2); RSTRING(str2)->as.heap.ptr += RSTRING(str2)->as.heap.len - len; RSTRING(str2)->as.heap.len = len; } else { str2 = rb_str_new5(str, p, len); if (len) rb_enc_cr_str_copy(str2, str); else { rb_enc_copy(str2, str); ENC_CODERANGE_SET(str2, ENC_CODERANGE_7BIT); } OBJ_INFECT(str2, str); } return str2; } VALUE rb_str_freeze(VALUE str) { if (STR_ASSOC_P(str)) { VALUE ary = RSTRING(str)->as.heap.aux.shared; OBJ_FREEZE(ary); } return rb_obj_freeze(str); } VALUE rb_str_dup_frozen(VALUE str) { if (STR_SHARED_P(str) && RSTRING(str)->as.heap.aux.shared) { VALUE shared = RSTRING(str)->as.heap.aux.shared; if (RSTRING_LEN(shared) == RSTRING_LEN(str)) { OBJ_FREEZE(shared); return shared; } } if (OBJ_FROZEN(str)) return str; str = rb_str_dup(str); OBJ_FREEZE(str); return str; } 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; } void rb_str_set_len(VALUE str, long len) { STR_SET_LEN(str, len); RSTRING_PTR(str)[len] = '\0'; } VALUE rb_str_resize(VALUE str, long len) { long slen; if (len < 0) { rb_raise(rb_eArgError, "negative string size (or size too big)"); } rb_str_modify(str); slen = RSTRING_LEN(str); if (len != slen) { if (STR_EMBED_P(str)) { char *ptr; if (len <= RSTRING_EMBED_LEN_MAX) { STR_SET_EMBED_LEN(str, len); RSTRING(str)->as.ary[len] = '\0'; return str; } ptr = ALLOC_N(char,len+1); MEMCPY(ptr, RSTRING(str)->as.ary, char, slen); RSTRING(str)->as.heap.ptr = ptr; STR_SET_NOEMBED(str); } else if (len <= RSTRING_EMBED_LEN_MAX) { char *ptr = RSTRING(str)->as.heap.ptr; STR_SET_EMBED(str); if (slen > 0) MEMCPY(RSTRING(str)->as.ary, ptr, char, len); RSTRING(str)->as.ary[len] = '\0'; STR_SET_EMBED_LEN(str, len); xfree(ptr); return str; } else if (slen < len || slen - len > 1024) { REALLOC_N(RSTRING(str)->as.heap.ptr, char, len+1); } if (!STR_NOCAPA_P(str)) { RSTRING(str)->as.heap.aux.capa = len; } RSTRING(str)->as.heap.len = len; RSTRING(str)->as.heap.ptr[len] = '\0'; /* sentinel */ } return str; } VALUE rb_str_buf_cat(VALUE str, const char *ptr, long len) { long capa, total; if (len == 0) return str; if (len < 0) { rb_raise(rb_eArgError, "negative string size (or size too big)"); } rb_str_modify(str); if (STR_ASSOC_P(str)) { FL_UNSET(str, STR_ASSOC); capa = RSTRING(str)->as.heap.aux.capa = RSTRING_LEN(str); } else if (STR_EMBED_P(str)) { capa = RSTRING_EMBED_LEN_MAX; } else { capa = RSTRING(str)->as.heap.aux.capa; } total = RSTRING_LEN(str)+len; if (capa <= total) { while (total > capa) { capa = (capa + 1) * 2; } RESIZE_CAPA(str, capa); } memcpy(RSTRING_PTR(str) + RSTRING_LEN(str), ptr, len); STR_SET_LEN(str, total); RSTRING_PTR(str)[total] = '\0'; /* sentinel */ return str; } VALUE rb_str_buf_cat2(VALUE str, const char *ptr) { return rb_str_buf_cat(str, ptr, strlen(ptr)); } VALUE rb_str_cat(VALUE str, const char *ptr, long len) { if (len < 0) { rb_raise(rb_eArgError, "negative string size (or size too big)"); } if (STR_ASSOC_P(str)) { rb_str_modify(str); if (STR_EMBED_P(str)) str_make_independent(str); REALLOC_N(RSTRING(str)->as.heap.ptr, char, RSTRING(str)->as.heap.len+len); memcpy(RSTRING(str)->as.heap.ptr + RSTRING(str)->as.heap.len, ptr, len); RSTRING(str)->as.heap.len += len; RSTRING(str)->as.heap.ptr[RSTRING(str)->as.heap.len] = '\0'; /* sentinel */ return str; } return rb_str_buf_cat(str, ptr, len); } VALUE rb_str_cat2(VALUE str, const char *ptr) { return rb_str_cat(str, ptr, strlen(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) { long capa, total, off = -1; int str_encindex = ENCODING_GET(str); int res_encindex; int str_cr, res_cr; int str_a8 = ENCODING_IS_ASCII8BIT(str); int ptr_a8 = ptr_encindex == 0; str_cr = ENC_CODERANGE(str); if (str_encindex == ptr_encindex) { if (str_cr == ENC_CODERANGE_UNKNOWN || (ptr_a8 && str_cr != ENC_CODERANGE_7BIT)) { ptr_cr = ENC_CODERANGE_UNKNOWN; } else if (ptr_cr == ENC_CODERANGE_UNKNOWN) { ptr_cr = coderange_scan(ptr, len, rb_enc_from_index(ptr_encindex)); } } else { rb_encoding *str_enc = rb_enc_from_index(str_encindex); rb_encoding *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 (str_a8 || 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) { incompatible: rb_raise(rb_eArgError, "append incompatible encoding strings: %s and %s", rb_enc_name(rb_enc_from_index(str_encindex)), rb_enc_name(rb_enc_from_index(ptr_encindex))); } 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_a8 ? str_encindex : ptr_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; res_cr = str_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)"); } if (ptr >= RSTRING_PTR(str) && ptr <= RSTRING_END(str)) { off = ptr - RSTRING_PTR(str); } rb_str_modify(str); if (len == 0) { ENCODING_CODERANGE_SET(str, res_encindex, res_cr); return str; } if (STR_ASSOC_P(str)) { FL_UNSET(str, STR_ASSOC); capa = RSTRING(str)->as.heap.aux.capa = RSTRING_LEN(str); } else if (STR_EMBED_P(str)) { capa = RSTRING_EMBED_LEN_MAX; } else { capa = RSTRING(str)->as.heap.aux.capa; } total = RSTRING_LEN(str)+len; if (capa <= total) { while (total > capa) { capa = (capa + 1) * 2; } RESIZE_CAPA(str, capa); } if (off != -1) { ptr = RSTRING_PTR(str) + off; } memcpy(RSTRING_PTR(str) + RSTRING_LEN(str), ptr, len); STR_SET_LEN(str, total); RSTRING_PTR(str)[total] = '\0'; // sentinel ENCODING_CODERANGE_SET(str, res_encindex, res_cr); return str; } 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) { 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); OBJ_INFECT(str, str2); ENC_CODERANGE_SET(str2, str2_cr); return str; } VALUE rb_str_append(VALUE str, VALUE str2) { rb_encoding *enc; int cr, cr2; StringValue(str2); if (RSTRING_LEN(str2) > 0 && STR_ASSOC_P(str)) { long len = RSTRING_LEN(str)+RSTRING_LEN(str2); enc = rb_enc_check(str, str2); cr = ENC_CODERANGE(str); if ((cr2 = ENC_CODERANGE(str2)) > cr) cr = cr2; rb_str_modify(str); REALLOC_N(RSTRING(str)->as.heap.ptr, char, len+1); memcpy(RSTRING(str)->as.heap.ptr + RSTRING(str)->as.heap.len, RSTRING_PTR(str2), RSTRING_LEN(str2)+1); RSTRING(str)->as.heap.len = len; rb_enc_associate(str, enc); ENC_CODERANGE_SET(str, cr); OBJ_INFECT(str, str2); return str; } return rb_str_buf_append(str, str2); } /* * call-seq: * str << fixnum => str * str.concat(fixnum) => str * str << obj => str * str.concat(obj) => str * * Append---Concatenates the given object to str. If the object is a * Fixnum, it is considered as a codepoint, and is converted * to a character before concatenation. * * a = "hello " * a << "world" #=> "hello world" * a.concat(33) #=> "hello world!" */ VALUE rb_str_concat(VALUE str1, VALUE str2) { if (FIXNUM_P(str2)) { rb_encoding *enc = STR_ENC_GET(str1); int c = FIX2INT(str2); int pos = RSTRING_LEN(str1); int len = rb_enc_codelen(c, enc); rb_str_resize(str1, pos+len); rb_enc_mbcput(c, RSTRING_PTR(str1)+pos, enc); return str1; } return rb_str_append(str1, str2); } typedef unsigned int ub4; /* unsigned 4-byte quantities */ typedef unsigned char ub1; /* unsigned 1-byte quantities */ #define hashsize(n) ((ub4)1<<(n)) #define hashmask(n) (hashsize(n)-1) /* -------------------------------------------------------------------- mix -- mix 3 32-bit values reversibly. For every delta with one or two bits set, and the deltas of all three high bits or all three low bits, whether the original value of a,b,c is almost all zero or is uniformly distributed, * If mix() is run forward or backward, at least 32 bits in a,b,c have at least 1/4 probability of changing. * If mix() is run forward, every bit of c will change between 1/3 and 2/3 of the time. (Well, 22/100 and 78/100 for some 2-bit deltas.) mix() was built out of 36 single-cycle latency instructions in a structure that could supported 2x parallelism, like so: a -= b; a -= c; x = (c>>13); b -= c; a ^= x; b -= a; x = (a<<8); c -= a; b ^= x; c -= b; x = (b>>13); ... Unfortunately, superscalar Pentiums and Sparcs can't take advantage of that parallelism. They've also turned some of those single-cycle latency instructions into multi-cycle latency instructions. Still, this is the fastest good hash I could find. There were about 2^^68 to choose from. I only looked at a billion or so. -------------------------------------------------------------------- */ #define mix(a,b,c) \ { \ a -= b; a -= c; a ^= (c>>13); \ b -= c; b -= a; b ^= (a<<8); \ c -= a; c -= b; c ^= (b>>13); \ a -= b; a -= c; a ^= (c>>12); \ b -= c; b -= a; b ^= (a<<16); \ c -= a; c -= b; c ^= (b>>5); \ a -= b; a -= c; a ^= (c>>3); \ b -= c; b -= a; b ^= (a<<10); \ c -= a; c -= b; c ^= (b>>15); \ } /* -------------------------------------------------------------------- hash() -- hash a variable-length key into a 32-bit value k : the key (the unaligned variable-length array of bytes) len : the length of the key, counting by bytes initval : can be any 4-byte value Returns a 32-bit value. Every bit of the key affects every bit of the return value. Every 1-bit and 2-bit delta achieves avalanche. About 6*len+35 instructions. The best hash table sizes are powers of 2. There is no need to do mod a prime (mod is sooo slow!). If you need less than 32 bits, use a bitmask. For example, if you need only 10 bits, do h = (h & hashmask(10)); In which case, the hash table should have hashsize(10) elements. If you are hashing n strings (ub1 **)k, do it like this: for (i=0, h=0; i= 12) { a += (k[0] +((ub4)k[1]<<8) +((ub4)k[2]<<16) +((ub4)k[3]<<24)); b += (k[4] +((ub4)k[5]<<8) +((ub4)k[6]<<16) +((ub4)k[7]<<24)); c += (k[8] +((ub4)k[9]<<8) +((ub4)k[10]<<16)+((ub4)k[11]<<24)); mix(a,b,c); k += 12; len -= 12; } /*------------------------------------- handle the last 11 bytes */ c += length; switch(len) /* all the case statements fall through */ { case 11: c+=((ub4)k[10]<<24); case 10: c+=((ub4)k[9]<<16); case 9 : c+=((ub4)k[8]<<8); /* the first byte of c is reserved for the length */ case 8 : b+=((ub4)k[7]<<24); case 7 : b+=((ub4)k[6]<<16); case 6 : b+=((ub4)k[5]<<8); case 5 : b+=k[4]; case 4 : a+=((ub4)k[3]<<24); case 3 : a+=((ub4)k[2]<<16); case 2 : a+=((ub4)k[1]<<8); case 1 : a+=k[0]; /* case 0: nothing left to add */ } mix(a,b,c); /*-------------------------------------------- report the result */ return c; } int rb_memhash(const void *ptr, long len) { return hash(ptr, len, 0); } int rb_str_hash(VALUE str) { return hash((const void *)RSTRING_PTR(str), RSTRING_LEN(str), 0); } int rb_str_hash_cmp(VALUE str1, VALUE str2) { int len; if (!rb_str_comparable(str1, str2)) return 1; if (RSTRING_LEN(str1) == (len = RSTRING_LEN(str2)) && memcmp(RSTRING_PTR(str1), RSTRING_PTR(str2), len) == 0) { return 0; } return 1; } /* * call-seq: * str.hash => fixnum * * Return a hash based on the string's length and content. */ static VALUE rb_str_hash_m(VALUE str) { int hval = rb_str_hash(str); return INT2FIX(hval); } #define lesser(a,b) (((a)>(b))?(b):(a)) int rb_str_comparable(VALUE str1, VALUE str2) { int idx1 = ENCODING_GET(str1); int idx2 = ENCODING_GET(str2); int rc1, rc2; if (idx1 == idx2) return Qtrue; rc1 = rb_enc_str_coderange(str1); rc2 = rb_enc_str_coderange(str2); if (rc1 == ENC_CODERANGE_7BIT) { if (rc2 == ENC_CODERANGE_7BIT) return Qtrue; if (rb_enc_asciicompat(rb_enc_from_index(idx1))) return Qtrue; } if (rc2 == ENC_CODERANGE_7BIT) { if (rb_enc_asciicompat(rb_enc_from_index(idx2))) return Qtrue; } return Qfalse; } int rb_str_cmp(VALUE str1, VALUE str2) { long len; int retval; rb_encoding *enc; enc = rb_enc_compatible(str1, str2); len = lesser(RSTRING_LEN(str1), RSTRING_LEN(str2)); retval = memcmp(RSTRING_PTR(str1), RSTRING_PTR(str2), len); if (retval == 0) { if (RSTRING_LEN(str1) == RSTRING_LEN(str2)) { if (!enc) { if (ENCODING_GET(str1) - ENCODING_GET(str2) > 0) return 1; return -1; } return 0; } if (RSTRING_LEN(str1) > RSTRING_LEN(str2)) return 1; return -1; } if (retval > 0) return 1; return -1; } /* * call-seq: * str == obj => true or false * * Equality---If obj is not a String, returns * false. Otherwise, returns true if str * <=> obj returns zero. */ VALUE rb_str_equal(VALUE str1, VALUE str2) { int len; if (str1 == str2) return Qtrue; if (TYPE(str2) != T_STRING) { if (!rb_respond_to(str2, rb_intern("to_str"))) { return Qfalse; } return rb_equal(str2, str1); } if (!rb_str_comparable(str1, str2)) return Qfalse; if (RSTRING_LEN(str1) == (len = RSTRING_LEN(str2)) && memcmp(RSTRING_PTR(str1), RSTRING_PTR(str2), len) == 0) { return Qtrue; } return Qfalse; } /* * call-seq: * str.eql?(other) => true or false * * Two strings are equal if the have the same length and content. */ static VALUE rb_str_eql(VALUE str1, VALUE str2) { if (TYPE(str2) != T_STRING || RSTRING_LEN(str1) != RSTRING_LEN(str2)) return Qfalse; if (!rb_str_comparable(str1, str2)) return Qfalse; if (memcmp(RSTRING_PTR(str1), RSTRING_PTR(str2), lesser(RSTRING_LEN(str1), RSTRING_LEN(str2))) == 0) return Qtrue; return Qfalse; } /* * call-seq: * str <=> other_str => -1, 0, +1 * * Comparison---Returns -1 if other_str is less than, 0 if * other_str is equal to, and +1 if other_str is greater than * str. If the strings are of different lengths, and the strings are * equal when compared up to the shortest length, then the longer string is * considered greater than the shorter one. In older versions of Ruby, setting * $= allowed case-insensitive comparisons; this is now deprecated * in favor of using String#casecmp. * * <=> is the basis for the methods <, * <=, >, >=, and between?, * included from module Comparable. The method * String#== does not use Comparable#==. * * "abcdef" <=> "abcde" #=> 1 * "abcdef" <=> "abcdef" #=> 0 * "abcdef" <=> "abcdefg" #=> -1 * "abcdef" <=> "ABCDEF" #=> 1 */ static VALUE rb_str_cmp_m(VALUE str1, VALUE str2) { long result; if (TYPE(str2) != T_STRING) { if (!rb_respond_to(str2, rb_intern("to_str"))) { return Qnil; } else if (!rb_respond_to(str2, rb_intern("<=>"))) { return Qnil; } else { VALUE tmp = rb_funcall(str2, rb_intern("<=>"), 1, str1); if (NIL_P(tmp)) return Qnil; if (!FIXNUM_P(tmp)) { return rb_funcall(LONG2FIX(0), '-', 1, tmp); } result = -FIX2LONG(tmp); } } else { result = rb_str_cmp(str1, str2); } return LONG2NUM(result); } /* * call-seq: * str.casecmp(other_str) => -1, 0, +1 * * Case-insensitive version of String#<=>. * * "abcdef".casecmp("abcde") #=> 1 * "aBcDeF".casecmp("abcdef") #=> 0 * "abcdef".casecmp("abcdefg") #=> -1 * "abcdef".casecmp("ABCDEF") #=> 0 */ static VALUE rb_str_casecmp(VALUE str1, VALUE str2) { long len; rb_encoding *enc; char *p1, *p1end, *p2, *p2end; StringValue(str2); 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); while (p1 < p1end && p2 < p2end) { int c1 = rb_enc_codepoint(p1, p1end, enc); int c2 = rb_enc_codepoint(p2, p2end, enc); if (c1 != c2) { c1 = rb_enc_toupper(c1, enc); c2 = rb_enc_toupper(c2, enc); if (c1 > c2) return INT2FIX(1); if (c1 < c2) return INT2FIX(-1); } len = rb_enc_codelen(c1, enc); p1 += len; p2 += len; } if (RSTRING_LEN(str1) == RSTRING_LEN(str2)) return INT2FIX(0); if (RSTRING_LEN(str1) > RSTRING_LEN(str2)) return INT2FIX(1); return INT2FIX(-1); } static long rb_str_index(VALUE str, VALUE sub, long offset) { long pos; char *s, *sptr; long len, slen; rb_encoding *enc; enc = rb_enc_check(str, sub); if (is_broken_string(sub)) { return -1; } len = str_strlen(str, enc); slen = str_strlen(sub, enc); if (offset < 0) { offset += len; if (offset < 0) return -1; } if (len - offset < slen) return -1; s = RSTRING_PTR(str); if (offset) { offset = str_offset(s, RSTRING_END(str), offset, enc, single_byte_optimizable(str)); s += offset; } if (slen == 0) return offset; /* need proceed one character at a time */ sptr = RSTRING_PTR(sub); slen = RSTRING_LEN(sub); len = RSTRING_LEN(str) - offset; for (;;) { char *t; pos = rb_memsearch(sptr, slen, s, len); if (pos < 0) return pos; t = rb_enc_right_char_head(s, s+pos, enc); if (t == s) break; if ((len -= t - s) <= 0) return -1; offset += t - s; s = t; } return pos + offset; } /* * call-seq: * str.index(substring [, offset]) => fixnum or nil * str.index(fixnum [, offset]) => fixnum or nil * str.index(regexp [, offset]) => fixnum or nil * * Returns the index of the first occurrence of the given substring, * character (fixnum), or pattern (regexp) in str. Returns * nil if not found. If the second parameter is present, it * specifies the position in the string to begin the search. * * "hello".index('e') #=> 1 * "hello".index('lo') #=> 3 * "hello".index('a') #=> nil * "hello".index(101) #=> 1 * "hello".index(/[aeiou]/, -3) #=> 4 */ 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, STR_ENC_GET(str)); if (pos < 0) { if (TYPE(sub) == T_REGEXP) { rb_backref_set(Qnil); } return Qnil; } } switch (TYPE(sub)) { case T_REGEXP: pos = rb_reg_adjust_startpos(sub, str, pos, 0); pos = rb_reg_search(sub, str, pos, 0); pos = rb_str_sublen(str, pos); break; default: { VALUE tmp; tmp = rb_check_string_type(sub); if (NIL_P(tmp)) { rb_raise(rb_eTypeError, "type mismatch: %s given", rb_obj_classname(sub)); } sub = tmp; } /* fall through */ case T_STRING: pos = rb_str_index(str, sub, pos); pos = rb_str_sublen(str, pos); break; } if (pos == -1) return Qnil; return LONG2NUM(pos); } static long rb_str_rindex(VALUE str, VALUE sub, long pos) { long len, slen; char *s, *sbeg, *e, *t; rb_encoding *enc; int singlebyte = single_byte_optimizable(str); enc = rb_enc_check(str, sub); if (is_broken_string(sub)) { return -1; } len = str_strlen(str, enc); slen = str_strlen(sub, enc); /* 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); e = RSTRING_END(str); t = RSTRING_PTR(sub); slen = RSTRING_LEN(sub); for (;;) { s = str_nth(sbeg, e, pos, enc, singlebyte); if (!s) return -1; if (memcmp(s, t, slen) == 0) { return pos; } if (pos == 0) break; pos--; } return -1; } /* * call-seq: * str.rindex(substring [, fixnum]) => fixnum or nil * str.rindex(fixnum [, fixnum]) => fixnum or nil * str.rindex(regexp [, fixnum]) => fixnum or nil * * Returns the index of the last occurrence of the given substring, * character (fixnum), or pattern (regexp) in str. Returns * nil if not found. If the second parameter is present, it * specifies the position in the string to end the search---characters beyond * this point will not be considered. * * "hello".rindex('e') #=> 1 * "hello".rindex('l') #=> 3 * "hello".rindex('a') #=> nil * "hello".rindex(101) #=> 1 * "hello".rindex(/[aeiou]/, -2) #=> 1 */ 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); if (rb_scan_args(argc, argv, "11", &sub, &vpos) == 2) { pos = NUM2LONG(vpos); if (pos < 0) { pos += len; if (pos < 0) { if (TYPE(sub) == T_REGEXP) { rb_backref_set(Qnil); } return Qnil; } } if (pos > len) pos = len; } else { pos = len; } switch (TYPE(sub)) { case T_REGEXP: /* enc = rb_get_check(str, sub); */ if (RREGEXP(sub)->len) { pos = rb_reg_adjust_startpos(sub, str, pos, 1); pos = rb_reg_search(sub, str, pos, 1); pos = rb_str_sublen(str, pos); } if (pos >= 0) return LONG2NUM(pos); break; default: { VALUE tmp; tmp = rb_check_string_type(sub); if (NIL_P(tmp)) { rb_raise(rb_eTypeError, "type mismatch: %s given", rb_obj_classname(sub)); } sub = tmp; } /* fall through */ case T_STRING: pos = rb_str_rindex(str, sub, pos); if (pos >= 0) return LONG2NUM(pos); break; } return Qnil; } /* * call-seq: * str =~ obj => fixnum or nil * * Match---If obj is a Regexp, use it as a pattern to match * against str,and returns the position the match starts, or * nil if there is no match. Otherwise, invokes * obj.=~, passing str as an argument. The default * =~ in Object returns false. * * "cat o' 9 tails" =~ /\d/ #=> 7 * "cat o' 9 tails" =~ 9 #=> false */ static VALUE rb_str_match(VALUE x, VALUE y) { switch (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, rb_intern("=~"), 1, x); } } static VALUE get_pat(VALUE, int); /* * call-seq: * str.match(pattern) => matchdata or nil * * Converts pattern to a Regexp (if it isn't already one), * then invokes its match method on str. If the second * parameter is present, it specifies the position in the string to begin the * search. * * 'hello'.match('(.)\1') #=> # * 'hello'.match('(.)\1')[0] #=> "ll" * 'hello'.match(/(.)\1/)[0] #=> "ll" * 'hello'.match('xx') #=> nil * * If a block is given, invoke the block with MatchData if match succeed, so * that you can write * * str.match(pat) {|m| ...} * * instead of * * if m = str.match(pat) * ... * end * * The return value is a value from block execution in this case. */ static VALUE rb_str_match_m(int argc, VALUE *argv, VALUE str) { VALUE re, result; if (argc < 1) rb_raise(rb_eArgError, "wrong number of arguments (%d for 1)", argc); re = argv[0]; argv[0] = str; result = rb_funcall2(get_pat(re, 0), rb_intern("match"), argc, argv); if (!NIL_P(result) && rb_block_given_p()) { return rb_yield(result); } return result; } enum neighbor_char { NEIGHBOR_NOT_CHAR, NEIGHBOR_FOUND, NEIGHBOR_WRAPPED }; static enum neighbor_char enc_succ_char(char *p, int len, rb_encoding *enc) { int i, l; 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) { int len2, 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, int len, rb_encoding *enc) { int i, l; 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) { int len2, 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, int len, rb_encoding *enc, char *carry) { enum neighbor_char ret; int c; int ctype; int range; char save[ONIGENC_CODE_TO_MBC_MAXLEN]; 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); 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; } /* * call-seq: * str.succ => new_str * str.next => new_str * * Returns the successor to str. The successor is calculated by * incrementing characters starting from the rightmost alphanumeric (or * the rightmost character if there are no alphanumerics) in the * string. Incrementing a digit always results in another digit, and * incrementing a letter results in another letter of the same case. * Incrementing nonalphanumerics uses the underlying character set's * collating sequence. * * If the increment generates a ``carry,'' the character to the left of * it is incremented. This process repeats until there is no carry, * adding an additional character if necessary. * * "abcd".succ #=> "abce" * "THX1138".succ #=> "THX1139" * "<>".succ #=> "<>" * "1999zzz".succ #=> "2000aaa" * "ZZZ9999".succ #=> "AAAA0000" * "***".succ #=> "**+" */ VALUE rb_str_succ(VALUE orig) { rb_encoding *enc; VALUE str; char *sbeg, *s, *e; int c = -1; long l; char carry[ONIGENC_CODE_TO_MBC_MAXLEN] = "\1"; int carry_pos = 0, carry_len = 1; str = rb_str_new5(orig, RSTRING_PTR(orig), RSTRING_LEN(orig)); rb_enc_cr_str_copy(str, orig); OBJ_INFECT(str, orig); if (RSTRING_LEN(str) == 0) return str; enc = STR_ENC_GET(orig); sbeg = RSTRING_PTR(str); s = e = sbeg + RSTRING_LEN(str); while ((s = rb_enc_prev_char(sbeg, s, enc)) != 0) { enum neighbor_char neighbor; if ((l = rb_enc_precise_mbclen(s, e, enc)) <= 0) continue; neighbor = enc_succ_alnum_char(s, l, enc, carry); if (neighbor == NEIGHBOR_NOT_CHAR) continue; if (neighbor == NEIGHBOR_FOUND) return str; c = 1; carry_pos = s - sbeg; carry_len = l; } if (c == -1) { /* str contains no alnum */ s = e; while ((s = rb_enc_prev_char(sbeg, s, enc)) != 0) { enum neighbor_char neighbor; if ((l = rb_enc_precise_mbclen(s, e, enc)) <= 0) continue; neighbor = enc_succ_char(s, l, enc); if (neighbor == NEIGHBOR_FOUND) return str; 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; } } RESIZE_CAPA(str, RSTRING_LEN(str) + carry_len); s = RSTRING_PTR(str) + carry_pos; memmove(s + carry_len, s, RSTRING_LEN(str) - carry_pos); memmove(s, carry, carry_len); STR_SET_LEN(str, RSTRING_LEN(str) + carry_len); RSTRING_PTR(str)[RSTRING_LEN(str)] = '\0'; rb_enc_str_coderange(str); return str; } /* * call-seq: * str.succ! => str * str.next! => str * * Equivalent to String#succ, but modifies the receiver in * place. */ static VALUE rb_str_succ_bang(VALUE str) { rb_str_shared_replace(str, rb_str_succ(str)); return str; } /* * call-seq: * str.upto(other_str, exclusive=false) {|s| block } => str * * Iterates through successive values, starting at str and * ending at other_str inclusive, passing each value in turn to * the block. The String#succ method is used to generate * each value. If optional second argument exclusive is omitted or is false, * the last value will be included; otherwise it will be excluded. * * "a8".upto("b6") {|s| print s, ' ' } * for s in "a8".."b6" * print s, ' ' * end * * produces: * * a8 a9 b0 b1 b2 b3 b4 b5 b6 * a8 a9 b0 b1 b2 b3 b4 b5 b6 */ static VALUE rb_str_upto(int argc, VALUE *argv, VALUE beg) { VALUE end, exclusive; VALUE current, after_end; ID succ; int n, excl; rb_encoding *enc; rb_scan_args(argc, argv, "11", &end, &exclusive); excl = RTEST(exclusive); succ = rb_intern("succ"); StringValue(end); enc = rb_enc_check(beg, end); if (RSTRING_LEN(beg) == 1 && RSTRING_LEN(end) == 1 && is_ascii_string(beg) && is_ascii_string(end)) { char c = RSTRING_PTR(beg)[0]; char e = RSTRING_PTR(end)[0]; if (c > e || (excl && c == e)) return beg; for (;;) { rb_yield(rb_enc_str_new(&c, 1, enc)); if (!excl && c == e) break; c++; if (excl && c == e) break; } return beg; } n = rb_str_cmp(beg, end); if (n > 0 || (excl && n == 0)) return beg; after_end = rb_funcall(end, succ, 0, 0); current = beg; while (!rb_str_equal(current, after_end)) { rb_yield(current); if (!excl && rb_str_equal(current, end)) break; current = rb_funcall(current, succ, 0, 0); StringValue(current); if (excl && rb_str_equal(current, end)) break; if (RSTRING_LEN(current) > RSTRING_LEN(end) || RSTRING_LEN(current) == 0) break; } return beg; } static VALUE rb_str_subpat(VALUE str, VALUE re, int nth) { if (rb_reg_search(re, str, 0, 0) >= 0) { return rb_reg_nth_match(nth, rb_backref_get()); } return Qnil; } static VALUE rb_str_aref(VALUE str, VALUE indx) { long idx; switch (TYPE(indx)) { case T_FIXNUM: idx = FIX2LONG(indx); num_index: str = rb_str_substr(str, idx, 1); if (!NIL_P(str) && RSTRING_LEN(str) == 0) return Qnil; return str; case T_REGEXP: return rb_str_subpat(str, indx, 0); case T_STRING: if (rb_str_index(str, indx, 0) != -1) return rb_str_dup(indx); return Qnil; default: /* check if indx is Range */ { long beg, len; VALUE tmp; len = str_strlen(str, STR_ENC_GET(str)); switch (rb_range_beg_len(indx, &beg, &len, len, 0)) { case Qfalse: break; case Qnil: return Qnil; default: tmp = rb_str_substr(str, beg, len); return tmp; } } idx = NUM2LONG(indx); goto num_index; } return Qnil; /* not reached */ } /* * call-seq: * str[fixnum] => new_str or nil * str[fixnum, fixnum] => new_str or nil * str[range] => new_str or nil * str[regexp] => new_str or nil * str[regexp, fixnum] => new_str or nil * str[other_str] => new_str or nil * str.slice(fixnum) => new_str or nil * str.slice(fixnum, fixnum) => new_str or nil * str.slice(range) => new_str or nil * str.slice(regexp) => new_str or nil * str.slice(regexp, fixnum) => new_str or nil * str.slice(other_str) => new_str or nil * * Element Reference---If passed a single Fixnum, returns a * substring of one character at that position. If passed two Fixnum * 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 * characters at offsets given by the range is returned. In all three cases, if * an offset is negative, it is counted from the end of str. Returns * nil if the initial offset falls outside the string, the length * is negative, or the beginning of the range is greater than the end. * * If a Regexp is supplied, the matching portion of str is * returned. If a numeric parameter follows the regular expression, that * component of the MatchData is returned instead. If a * String is given, that string is returned if it occurs in * str. In both cases, nil is returned if there is no * match. * * a = "hello there" * a[1] #=> "e" * a[1,3] #=> "ell" * a[1..3] #=> "ell" * a[-3,2] #=> "er" * a[-4..-2] #=> "her" * a[12..-1] #=> nil * a[-2..-4] #=> "" * a[/[aeiou](.)\1/] #=> "ell" * a[/[aeiou](.)\1/, 0] #=> "ell" * a[/[aeiou](.)\1/, 1] #=> "l" * a[/[aeiou](.)\1/, 2] #=> nil * a["lo"] #=> "lo" * a["bye"] #=> nil */ static VALUE rb_str_aref_m(int argc, VALUE *argv, VALUE str) { if (argc == 2) { if (TYPE(argv[0]) == T_REGEXP) { return rb_str_subpat(str, argv[0], NUM2INT(argv[1])); } return rb_str_substr(str, NUM2LONG(argv[0]), NUM2LONG(argv[1])); } if (argc != 1) { rb_raise(rb_eArgError, "wrong number of arguments (%d for 1)", argc); } return rb_str_aref(str, argv[0]); } static void rb_str_splice_0(VALUE str, long beg, long len, VALUE val) { rb_str_modify(str); if (len < RSTRING_LEN(val)) { /* expand string */ RESIZE_CAPA(str, RSTRING_LEN(str) + RSTRING_LEN(val) - len + 1); } if (RSTRING_LEN(val) != len) { memmove(RSTRING_PTR(str) + beg + RSTRING_LEN(val), RSTRING_PTR(str) + beg + len, RSTRING_LEN(str) - (beg + len)); } if (RSTRING_LEN(val) < beg && len < 0) { MEMZERO(RSTRING_PTR(str) + RSTRING_LEN(str), char, -len); } if (RSTRING_LEN(val) > 0) { memmove(RSTRING_PTR(str)+beg, RSTRING_PTR(val), RSTRING_LEN(val)); } STR_SET_LEN(str, RSTRING_LEN(str) + RSTRING_LEN(val) - len); if (RSTRING_PTR(str)) { RSTRING_PTR(str)[RSTRING_LEN(str)] = '\0'; } OBJ_INFECT(str, val); } static void rb_str_splice(VALUE str, long beg, long len, VALUE val) { long slen; char *p, *e; rb_encoding *enc; int singlebyte = single_byte_optimizable(str); if (len < 0) rb_raise(rb_eIndexError, "negative length %ld", len); StringValue(val); rb_str_modify(str); enc = rb_enc_check(str, val); slen = str_strlen(str, enc); if (slen < beg) { out_of_range: rb_raise(rb_eIndexError, "index %ld out of string", beg); } if (beg < 0) { if (-beg > slen) { goto out_of_range; } beg += slen; } if (slen < len || slen < beg + len) { len = slen - beg; } 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); } void rb_str_update(VALUE str, long beg, long len, VALUE val) { rb_str_splice(str, beg, len, val); } static void rb_str_subpat_set(VALUE str, VALUE re, int nth, VALUE val) { 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(); regs = RMATCH_REGS(match); if (nth >= regs->num_regs) { out_of_range: rb_raise(rb_eIndexError, "index %d out of regexp", nth); } if (nth < 0) { if (-nth >= regs->num_regs) { goto out_of_range; } 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, 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_FIXNUM: idx = FIX2LONG(indx); num_index: rb_str_splice(str, idx, 1, val); return val; case T_REGEXP: rb_str_subpat_set(str, indx, 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, 0), val); return val; default: /* check if indx is Range */ { long beg, len; if (rb_range_beg_len(indx, &beg, &len, str_strlen(str, 0), 2)) { rb_str_splice(str, beg, len, val); return val; } } idx = NUM2LONG(indx); goto num_index; } } /* * call-seq: * str[fixnum] = new_str * str[fixnum, fixnum] = new_str * str[range] = aString * str[regexp] = new_str * str[regexp, fixnum] = new_str * str[other_str] = new_str * * Element Assignment---Replaces some or all of the content of str. 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 Fixnum allows you to specify * which portion of the match to replace (effectively using the * MatchData indexing rules. The forms that take a * Fixnum will raise an IndexError if the value is * out of range; the Range form will raise a * RangeError, and the Regexp and String * forms will silently ignore the assignment. */ static VALUE rb_str_aset_m(int argc, VALUE *argv, VALUE str) { if (argc == 3) { if (TYPE(argv[0]) == T_REGEXP) { rb_str_subpat_set(str, argv[0], NUM2INT(argv[1]), argv[2]); } else { rb_str_splice(str, NUM2LONG(argv[0]), NUM2LONG(argv[1]), argv[2]); } return argv[2]; } if (argc != 2) { rb_raise(rb_eArgError, "wrong number of arguments (%d for 2)", argc); } return rb_str_aset(str, argv[0], argv[1]); } /* * call-seq: * str.insert(index, other_str) => str * * Inserts other_str before the character at the given * index, modifying str. Negative indices count from the * end of the string, and insert after the given character. * The intent is insert aString so that it starts at the given * index. * * "abcd".insert(0, 'X') #=> "Xabcd" * "abcd".insert(3, 'X') #=> "abcXd" * "abcd".insert(4, 'X') #=> "abcdX" * "abcd".insert(-3, 'X') #=> "abXcd" * "abcd".insert(-1, 'X') #=> "abcdX" */ 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!(fixnum) => fixnum or nil * str.slice!(fixnum, fixnum) => 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 str, and returns the portion * deleted. * * string = "this is a string" * string.slice!(2) #=> 105 * 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; VALUE buf[3]; int i; if (argc < 1 || 2 < argc) { rb_raise(rb_eArgError, "wrong number of arguments (%d for 1)", argc); } for (i=0; i str or nil * str.sub!(pattern) {|match| block } => str or nil * * Performs the substitutions of String#sub in place, * returning str, or nil if no substitutions were * performed. */ static VALUE rb_str_sub_bang(int argc, VALUE *argv, VALUE str) { VALUE pat, repl, match, hash = Qnil; struct re_registers *regs; int iter = 0; int tainted = 0; long plen; if (argc == 1 && rb_block_given_p()) { iter = 1; } else if (argc == 2) { repl = argv[1]; hash = rb_check_convert_type(argv[1], T_HASH, "Hash", "to_hash"); if (NIL_P(hash)) { StringValue(repl); } if (OBJ_TAINTED(repl)) tainted = 1; } else { rb_raise(rb_eArgError, "wrong number of arguments (%d for 2)", argc); } pat = get_pat(argv[0], 1); if (rb_reg_search(pat, str, 0, 0) >= 0) { rb_encoding *enc; int cr = ENC_CODERANGE(str); match = rb_backref_get(); regs = RMATCH_REGS(match); if (iter || !NIL_P(hash)) { char *p = RSTRING_PTR(str); long len = RSTRING_LEN(str); if (iter) { rb_match_busy(match); repl = rb_obj_as_string(rb_yield(rb_reg_nth_match(0, match))); } else { repl = rb_hash_aref(hash, rb_str_subseq(str, BEG(0), END(0) - BEG(0))); repl = rb_obj_as_string(repl); } str_mod_check(str, p, len); str_frozen_check(str); if (iter) rb_backref_set(match); } else { repl = rb_reg_regsub(repl, str, regs, pat); } enc = rb_enc_compatible(str, repl); if (!enc) { rb_encoding *str_enc = STR_ENC_GET(str); if (coderange_scan(RSTRING_PTR(str), BEG(0), str_enc) != ENC_CODERANGE_7BIT || coderange_scan(RSTRING_PTR(str)+END(0), RSTRING_LEN(str)-END(0), str_enc) != ENC_CODERANGE_7BIT) { rb_raise(rb_eArgError, "character encodings differ: %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 (OBJ_TAINTED(repl)) tainted = 1; if (ENC_CODERANGE_UNKNOWN < cr && cr < ENC_CODERANGE_BROKEN) { int cr2 = ENC_CODERANGE(repl); if (cr2 == ENC_CODERANGE_UNKNOWN || cr2 > cr) cr = cr2; } plen = END(0) - BEG(0); if (RSTRING_LEN(repl) > plen) { RESIZE_CAPA(str, RSTRING_LEN(str) + RSTRING_LEN(repl) - plen); } if (RSTRING_LEN(repl) != plen) { memmove(RSTRING_PTR(str) + BEG(0) + RSTRING_LEN(repl), RSTRING_PTR(str) + BEG(0) + plen, RSTRING_LEN(str) - BEG(0) - plen); } memcpy(RSTRING_PTR(str) + BEG(0), RSTRING_PTR(repl), RSTRING_LEN(repl)); STR_SET_LEN(str, RSTRING_LEN(str) + RSTRING_LEN(repl) - plen); RSTRING_PTR(str)[RSTRING_LEN(str)] = '\0'; ENC_CODERANGE_SET(str, cr); if (tainted) OBJ_TAINT(str); return str; } return Qnil; } /* * call-seq: * str.sub(pattern, replacement) => new_str * str.sub(pattern) {|match| block } => new_str * * Returns a copy of str with the first occurrence of * pattern replaced with either replacement or the value of the * block. The pattern will typically be a Regexp; if it is * a String then no regular expression metacharacters will be * interpreted (that is /\d/ will match a digit, but * '\d' will match a backslash followed by a 'd'). * * If the method call specifies replacement, special variables such as * $& will not be useful, as substitution into the string occurs * before the pattern match starts. However, the sequences \1, * \2, \k, etc., may be used. * * In the block form, the current match string is passed in as a parameter, and * variables such as $1, $2, $`, * $&, and $' will be set appropriately. The value * returned by the block will be substituted for the match on each call. * * The result inherits any tainting in the original string or any supplied * replacement string. * * "hello".sub(/[aeiou]/, '*') #=> "h*llo" * "hello".sub(/([aeiou])/, '<\1>') #=> "hllo" * "hello".sub(/./) {|s| s[0].to_s + ' ' } #=> "104 ello" * "hello".sub(/(?[aeiou])/, '*\k*') #=> "h*e*llo" */ static VALUE rb_str_sub(int argc, VALUE *argv, VALUE str) { str = rb_str_dup(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, repl, match, dest, hash = Qnil; struct re_registers *regs; long beg, n; long offset, blen, slen, len; int iter = 0; char *sp, *cp; int tainted = 0; rb_encoding *str_enc; switch (argc) { case 1: RETURN_ENUMERATOR(str, argc, argv); iter = 1; break; case 2: repl = argv[1]; hash = rb_check_convert_type(argv[1], T_HASH, "Hash", "to_hash"); if (NIL_P(hash)) { StringValue(repl); } if (OBJ_TAINTED(repl)) tainted = 1; break; default: rb_raise(rb_eArgError, "wrong number of arguments (%d for 2)", argc); } pat = get_pat(argv[0], 1); offset=0; n=0; beg = rb_reg_search(pat, str, 0, 0); if (beg < 0) { if (bang) return Qnil; /* no match, no substitution */ return rb_str_dup(str); } 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); do { n++; match = rb_backref_get(); regs = RMATCH_REGS(match); if (iter || !NIL_P(hash)) { if (iter) { rb_match_busy(match); val = rb_obj_as_string(rb_yield(rb_reg_nth_match(0, match))); } else { val = rb_hash_aref(hash, rb_str_subseq(str, BEG(0), END(0) - BEG(0))); val = rb_obj_as_string(val); } str_mod_check(str, sp, slen); if (bang) str_frozen_check(str); if (val == dest) { /* paranoid check [ruby-dev:24827] */ rb_raise(rb_eRuntimeError, "block should not cheat"); } if (iter) rb_backref_set(match); } else { val = rb_reg_regsub(repl, str, regs, pat); } if (OBJ_TAINTED(val)) tainted = 1; len = beg - offset; /* copy pre-match substr */ if (len) { rb_enc_str_buf_cat(dest, cp, len, str_enc); } rb_str_buf_append(dest, val); offset = END(0); if (BEG(0) == END(0)) { /* * Always consume at least one character of the input string * in order to prevent infinite loops. */ if (RSTRING_LEN(str) <= END(0)) break; len = rb_enc_mbclen(RSTRING_PTR(str)+END(0), RSTRING_END(str), str_enc); rb_enc_str_buf_cat(dest, RSTRING_PTR(str)+END(0), len, str_enc); offset = END(0) + len; } cp = RSTRING_PTR(str) + offset; if (offset > RSTRING_LEN(str)) break; beg = rb_reg_search(pat, str, offset, 0); } while (beg >= 0); if (RSTRING_LEN(str) > offset) { rb_enc_str_buf_cat(dest, cp, RSTRING_LEN(str) - offset, str_enc); } rb_backref_set(match); if (bang) { rb_str_shared_replace(str, dest); } else { RBASIC(dest)->klass = rb_obj_class(str); OBJ_INFECT(dest, str); str = dest; } if (tainted) OBJ_TAINT(str); return str; } /* * call-seq: * str.gsub!(pattern, replacement) => str or nil * str.gsub!(pattern) {|match| block } => str or nil * * Performs the substitutions of String#gsub in place, returning * str, or nil if no substitutions were performed. */ static VALUE rb_str_gsub_bang(int argc, VALUE *argv, VALUE str) { return str_gsub(argc, argv, str, 1); } /* * call-seq: * str.gsub(pattern, replacement) => new_str * str.gsub(pattern) {|match| block } => new_str * * Returns a copy of str with all occurrences of pattern * replaced with either replacement or the value of the block. The * pattern will typically be a Regexp; if it is a * String then no regular expression metacharacters will be * interpreted (that is /\d/ will match a digit, but * '\d' will match a backslash followed by a 'd'). * * If a string is used as the replacement, special variables from the match * (such as $& and $1) cannot be substituted into it, * as substitution into the string occurs before the pattern match * starts. However, the sequences \1, \2, * \k, and so on may be used to interpolate * successive groups in the match. * * In the block form, the current match string is passed in as a parameter, and * variables such as $1, $2, $`, * $&, and $' will be set appropriately. The value * returned by the block will be substituted for the match on each call. * * The result inherits any tainting in the original string or any supplied * replacement string. * * "hello".gsub(/[aeiou]/, '*') #=> "h*ll*" * "hello".gsub(/([aeiou])/, '<\1>') #=> "hll" * "hello".gsub(/./) {|s| s[0].to_s + ' '} #=> "104 101 108 108 111 " * "hello".gsub(/(?[aeiou])/, '{\k}') #=> "h{e}ll{o}" */ 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 and taintedness of str with the corresponding * values in other_str. * * s = "hello" #=> "hello" * s.replace "world" #=> "world" */ static VALUE rb_str_replace(VALUE str, VALUE str2) { long len; if (str == str2) return str; StringValue(str2); len = RSTRING_LEN(str2); if (STR_ASSOC_P(str2)) { str2 = rb_str_new4(str2); } if (STR_SHARED_P(str2)) { if (str_independent(str) && !STR_EMBED_P(str)) { free(RSTRING_PTR(str)); } STR_SET_NOEMBED(str); RSTRING(str)->as.heap.len = len; RSTRING(str)->as.heap.ptr = RSTRING_PTR(str2); FL_SET(str, ELTS_SHARED); FL_UNSET(str, STR_ASSOC); RSTRING(str)->as.heap.aux.shared = RSTRING(str2)->as.heap.aux.shared; } else { rb_str_modify(str); str_replace_shared(str, rb_str_new4(str2)); } OBJ_INFECT(str, str2); rb_enc_cr_str_exact_copy(str, str2); return str; } /* * call-seq: * string.clear -> string * * Makes string empty. * * a = "abcde" * a.clear #=> "" */ static VALUE rb_str_clear(VALUE str) { /* rb_str_modify() */ /* no need for str_make_independent */ if (str_independent(str) && !STR_EMBED_P(str)) { free(RSTRING_PTR(str)); } STR_SET_EMBED(str); STR_SET_EMBED_LEN(str, 0); RSTRING_PTR(str)[0] = 0; ENC_CODERANGE_CLEAR(str); 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 indexth 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, int) => int * * modifies the indexth byte as int. */ static VALUE rb_str_setbyte(VALUE str, VALUE index, VALUE value) { long pos = NUM2LONG(index); int byte = NUM2INT(value); rb_str_modify(str); if (pos < -RSTRING_LEN(str) || RSTRING_LEN(str) <= pos) rb_raise(rb_eIndexError, "index %ld out of string", pos); if (pos < 0) pos += RSTRING_LEN(str); RSTRING_PTR(str)[pos] = byte; return value; } /* * call-seq: * str.reverse => new_str * * Returns a new string with the characters from str in reverse order. * * "stressed".reverse #=> "desserts" */ static VALUE rb_str_reverse(VALUE str) { rb_encoding *enc; VALUE obj; char *s, *e, *p; if (RSTRING_LEN(str) <= 1) return rb_str_dup(str); enc = STR_ENC_GET(str); obj = rb_str_new5(str, 0, RSTRING_LEN(str)); s = RSTRING_PTR(str); e = RSTRING_END(str); p = RSTRING_END(obj); if (RSTRING_LEN(str) > 1) { if (rb_enc_mbmaxlen(enc) == 1) { while (s < e) { *--p = *s++; } } else { while (s < e) { int clen = rb_enc_mbclen(s, e, enc); p -= clen; memcpy(p, s, clen); s += clen; } } } STR_SET_LEN(obj, RSTRING_LEN(str)); OBJ_INFECT(obj, str); rb_enc_associate(obj, enc); return obj; } /* * call-seq: * str.reverse! => str * * Reverses str in place. */ static VALUE rb_str_reverse_bang(VALUE str) { rb_encoding *enc = STR_ENC_GET(str); char *s, *e, c; if (RSTRING_LEN(str) > 1) { rb_str_modify(str); s = RSTRING_PTR(str); e = RSTRING_END(str) - 1; if (rb_enc_mbmaxlen(enc) == 1) { while (s < e) { c = *s; *s++ = *e; *e-- = c; } } else { rb_str_shared_replace(str, rb_str_reverse(str)); } } return str; } /* * call-seq: * str.include? other_str => true or false * str.include? fixnum => true or false * * Returns true if str 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 str as an * integer base base (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 str, 0 is returned. This method never raises an * exception. * * "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) { VALUE b; int base; rb_scan_args(argc, argv, "01", &b); if (argc == 0) base = 10; else base = NUM2INT(b); if (base < 0) { rb_raise(rb_eArgError, "invalid radix %d", base); } return rb_str_to_inum(str, base, Qfalse); } /* * call-seq: * str.to_f => float * * Returns the result of interpreting leading characters in str 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 str, * 0.0 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 DOUBLE2NUM(rb_str_to_dbl(str, Qfalse)); } /* * call-seq: * str.to_s => str * str.to_str => str * * Returns the receiver. */ static VALUE rb_str_to_s(VALUE str) { if (rb_obj_class(str) != rb_cString) { VALUE dup = str_alloc(rb_cString); rb_str_replace(dup, str); return dup; } return str; } static void str_cat_char(VALUE str, int c, rb_encoding *enc) { char s[16]; int n = rb_enc_codelen(c, enc); rb_enc_mbcput(c, s, enc); rb_enc_str_buf_cat(str, s, n, enc); } static void prefix_escape(VALUE str, int c, rb_encoding *enc) { str_cat_char(str, '\\', enc); str_cat_char(str, c, enc); } /* * 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) { rb_encoding *enc = STR_ENC_GET(str); char *p, *pend; VALUE result = rb_str_buf_new2(""); if (!rb_enc_asciicompat(enc)) enc = rb_usascii_encoding(); rb_enc_associate(result, enc); str_cat_char(result, '"', enc); p = RSTRING_PTR(str); pend = RSTRING_END(str); while (p < pend) { int c; int n; int cc; n = rb_enc_precise_mbclen(p, pend, enc); if (!MBCLEN_CHARFOUND_P(n)) { p++; n = 1; goto escape_codepoint; } n = MBCLEN_CHARFOUND_LEN(n); c = rb_enc_codepoint(p, pend, enc); n = rb_enc_codelen(c, enc); p += n; if (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 == '{')))) { prefix_escape(result, c, enc); } else if (c == '\n') { prefix_escape(result, 'n', enc); } else if (c == '\r') { prefix_escape(result, 'r', enc); } else if (c == '\t') { prefix_escape(result, 't', enc); } else if (c == '\f') { prefix_escape(result, 'f', enc); } else if (c == '\013') { prefix_escape(result, 'v', enc); } else if (c == '\010') { prefix_escape(result, 'b', enc); } else if (c == '\007') { prefix_escape(result, 'a', enc); } else if (c == 033) { prefix_escape(result, 'e', enc); } else if (rb_enc_isprint(c, enc)) { rb_enc_str_buf_cat(result, p-n, n, enc); } else { char buf[5]; char *s; char *q; escape_codepoint: for (q = p-n; q < p; q++) { s = buf; sprintf(buf, "\\x%02X", *q & 0377); while (*s) { str_cat_char(result, *s++, enc); } } } } str_cat_char(result, '"', enc); OBJ_INFECT(result, str); return result; } #define IS_EVSTR(p,e) ((p) < (e) && (*(p) == '$' || *(p) == '@' || *(p) == '{')) /* * call-seq: * str.dump => new_str * * Produces a version of str with all nonprinting characters replaced by * \nnn notation and all special characters escaped. */ VALUE rb_str_dump(VALUE str) { rb_encoding *enc0 = rb_enc_get(str); long len; const char *p, *pend; char *q, *qend; VALUE result; len = 2; /* "" */ p = RSTRING_PTR(str); pend = p + RSTRING_LEN(str); while (p < pend) { unsigned char c = *p++; switch (c) { case '"': case '\\': case '\n': case '\r': case '\t': case '\f': case '\013': case '\010': case '\007': case '\033': len += 2; break; case '#': len += IS_EVSTR(p, pend) ? 2 : 1; break; default: if (ISPRINT(c)) { len++; } else { len += 4; /* \xNN */ } break; } } if (!rb_enc_asciicompat(enc0)) { len += 19; /* ".force_encoding('')" */ len += strlen(enc0->name); } result = rb_str_new5(str, 0, len); p = RSTRING_PTR(str); pend = p + RSTRING_LEN(str); q = RSTRING_PTR(result); qend = q + len; *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++ = '\\'; sprintf(q, "x%02X", c); q += 3; } } *q++ = '"'; if (!rb_enc_asciicompat(enc0)) { sprintf(q, ".force_encoding(\"%s\")", enc0->name); enc0 = rb_ascii8bit_encoding(); } OBJ_INFECT(result, str); /* result from dump is ASCII */ rb_enc_associate(result, enc0); return result; } /* * call-seq: * str.upcase! => str or nil * * Upcases the contents of str, returning nil if no changes * were made. * Note: case replacement is effective only in ASCII region. */ static VALUE rb_str_upcase_bang(VALUE str) { rb_encoding *enc; char *s, *send; int modify = 0; rb_str_modify(str); enc = STR_ENC_GET(str); s = RSTRING_PTR(str); send = RSTRING_END(str); while (s < send) { int c = rb_enc_codepoint(s, send, enc); if (rb_enc_islower(c, enc)) { /* assuming toupper returns codepoint with same size */ rb_enc_mbcput(rb_enc_toupper(c, enc), s, enc); modify = 1; } s += rb_enc_codelen(c, enc); } if (modify) return str; return Qnil; } /* * call-seq: * str.upcase => new_str * * Returns a copy of str with all lowercase letters replaced with their * uppercase counterparts. The operation is locale insensitive---only * characters ``a'' to ``z'' are affected. * Note: case replacement is effective only in ASCII region. * * "hEllO".upcase #=> "HELLO" */ static VALUE rb_str_upcase(VALUE str) { str = rb_str_dup(str); rb_str_upcase_bang(str); return str; } /* * call-seq: * str.downcase! => str or nil * * Downcases the contents of str, returning nil if no * changes were made. * Note: case replacement is effective only in ASCII region. */ static VALUE rb_str_downcase_bang(VALUE str) { rb_encoding *enc; char *s, *send; int modify = 0; rb_str_modify(str); enc = STR_ENC_GET(str); s = RSTRING_PTR(str); send = RSTRING_END(str); while (s < send) { int c = rb_enc_codepoint(s, send, enc); if (rb_enc_isupper(c, enc)) { /* assuming toupper returns codepoint with same size */ rb_enc_mbcput(rb_enc_tolower(c, enc), s, enc); modify = 1; } s += rb_enc_codelen(c, enc); } if (modify) return str; return Qnil; } /* * call-seq: * str.downcase => new_str * * Returns a copy of str with all uppercase letters replaced with their * lowercase counterparts. The operation is locale insensitive---only * characters ``A'' to ``Z'' are affected. * Note: case replacement is effective only in ASCII region. * * "hEllO".downcase #=> "hello" */ static VALUE rb_str_downcase(VALUE str) { str = rb_str_dup(str); rb_str_downcase_bang(str); return str; } /* * call-seq: * str.capitalize! => str or nil * * Modifies str by converting the first character to uppercase and the * remainder to lowercase. Returns nil if no changes are made. * Note: case conversion is effective only in ASCII region. * * a = "hello" * a.capitalize! #=> "Hello" * a #=> "Hello" * a.capitalize! #=> nil */ static VALUE rb_str_capitalize_bang(VALUE str) { rb_encoding *enc; char *s, *send; int modify = 0; int c; rb_str_modify(str); enc = STR_ENC_GET(str); if (RSTRING_LEN(str) == 0 || !RSTRING_PTR(str)) return Qnil; s = RSTRING_PTR(str); send = RSTRING_END(str); c = rb_enc_codepoint(s, send, enc); if (rb_enc_islower(c, enc)) { rb_enc_mbcput(rb_enc_toupper(c, enc), s, enc); modify = 1; } s += rb_enc_codelen(c, enc); while (s < send) { c = rb_enc_codepoint(s, send, enc); if (rb_enc_isupper(c, enc)) { rb_enc_mbcput(rb_enc_tolower(c, enc), s, enc); modify = 1; } s += rb_enc_codelen(c, enc); } if (modify) return str; return Qnil; } /* * call-seq: * str.capitalize => new_str * * Returns a copy of str with the first character converted to uppercase * and the remainder to lowercase. * Note: case conversion is effective only in ASCII region. * * "hello".capitalize #=> "Hello" * "HELLO".capitalize #=> "Hello" * "123ABC".capitalize #=> "123abc" */ static VALUE rb_str_capitalize(VALUE str) { str = rb_str_dup(str); rb_str_capitalize_bang(str); return str; } /* * call-seq: * str.swapcase! => str or nil * * Equivalent to String#swapcase, but modifies the receiver in * place, returning str, or nil if no changes were made. * Note: case conversion is effective only in ASCII region. */ static VALUE rb_str_swapcase_bang(VALUE str) { rb_encoding *enc; char *s, *send; int modify = 0; rb_str_modify(str); enc = STR_ENC_GET(str); s = RSTRING_PTR(str); send = RSTRING_END(str); while (s < send) { int c = rb_enc_codepoint(s, send, enc); if (rb_enc_isupper(c, enc)) { /* assuming toupper returns codepoint with same size */ rb_enc_mbcput(rb_enc_tolower(c, enc), s, enc); modify = 1; } else if (rb_enc_islower(c, enc)) { /* assuming toupper returns codepoint with same size */ rb_enc_mbcput(rb_enc_toupper(c, enc), s, enc); modify = 1; } s += rb_enc_codelen(c, enc); } if (modify) return str; return Qnil; } /* * call-seq: * str.swapcase => new_str * * Returns a copy of str with uppercase alphabetic characters converted * to lowercase and lowercase characters converted to uppercase. * Note: case conversion is effective only in ASCII region. * * "Hello".swapcase #=> "hELLO" * "cYbEr_PuNk11".swapcase #=> "CyBeR_pUnK11" */ static VALUE rb_str_swapcase(VALUE str) { str = rb_str_dup(str); rb_str_swapcase_bang(str); return str; } typedef unsigned char *USTR; struct tr { int gen, now, max; char *p, *pend; }; static int trnext(struct tr *t, rb_encoding *enc) { for (;;) { if (!t->gen) { if (t->p == t->pend) return -1; if (t->p < t->pend - 1 && *t->p == '\\') { t->p++; } t->now = rb_enc_codepoint(t->p, t->pend, enc); t->p += rb_enc_codelen(t->now, enc); if (t->p < t->pend - 1 && *t->p == '-') { t->p++; if (t->p < t->pend) { int c = rb_enc_codepoint(t->p, t->pend, enc); t->p += rb_enc_codelen(c, enc); if (t->now > c) continue; t->gen = 1; t->max = c; } } return t->now; } else 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) { SIGNED_VALUE trans[256]; rb_encoding *enc, *e1, *e2; struct tr trsrc, trrepl; int cflag = 0; int c, last = 0, modify = 0, i; char *s, *send; VALUE hash = 0; StringValue(src); StringValue(repl); if (RSTRING_LEN(str) == 0 || !RSTRING_PTR(str)) return Qnil; trsrc.p = RSTRING_PTR(src); trsrc.pend = trsrc.p + RSTRING_LEN(src); if (RSTRING_LEN(src) >= 2 && RSTRING_PTR(src)[0] == '^') { cflag++; trsrc.p++; } if (RSTRING_LEN(repl) == 0) { return rb_str_delete_bang(1, &src, 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); } 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)) >= 0) { if (c < 256) { trans[c] = -1; } else { if (!hash) hash = rb_hash_new(); rb_hash_aset(hash, INT2NUM(c), Qtrue); } } while ((c = trnext(&trrepl, enc)) >= 0) /* retrieve last replacer */; last = trrepl.now; for (i=0; i<256; i++) { if (trans[i] >= 0) { trans[i] = last; } } } else { int r; for (i=0; i<256; i++) { trans[i] = -1; } while ((c = trnext(&trsrc, enc)) >= 0) { r = trnext(&trrepl, enc); if (r == -1) r = trrepl.now; if (c < 256) { trans[c] = INT2NUM(r); } else { if (!hash) hash = rb_hash_new(); rb_hash_aset(hash, INT2NUM(c), INT2NUM(r)); } } } rb_str_modify(str); s = RSTRING_PTR(str); send = RSTRING_END(str); if (sflag) { int clen, tlen, max = RSTRING_LEN(str); int offset, save = -1; char *buf = ALLOC_N(char, max), *t = buf; VALUE v; if (cflag) tlen = rb_enc_codelen(last, enc); while (s < send) { c = rb_enc_codepoint(s, send, enc); tlen = clen = rb_enc_codelen(c, enc); s += clen; if (c < 256) { v = trans[c] >= 0 ? trans[c] : Qnil; } else { v = hash ? rb_hash_aref(hash, INT2NUM(c)) : Qnil; } if (!NIL_P(v)) { if (!cflag) { c = NUM2INT(v); if (save == c) continue; save = c; tlen = rb_enc_codelen(c, enc); modify = 1; } else { save = c = last; modify = 1; } } else { save = -1; } while (t - buf + tlen >= max) { offset = t - buf; max *= 2; REALLOC_N(buf, char, max); t = buf + offset; } rb_enc_mbcput(c, t, enc); t += tlen; } *t = '\0'; RSTRING(str)->as.heap.ptr = 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) { while (s < send) { c = (unsigned char)*s; if (trans[c] >= 0) { if (!cflag) { c = FIX2INT(trans[c]); *s = c; modify = 1; } else { *s = last; modify = 1; } } s++; } } else { int clen, tlen, max = RSTRING_LEN(str) * 1.2; int offset; char *buf = ALLOC_N(char, max), *t = buf; VALUE v; if (cflag) tlen = rb_enc_codelen(last, enc); while (s < send) { c = rb_enc_codepoint(s, send, enc); tlen = clen = rb_enc_codelen(c, enc); if (c < 256) { v = trans[c] >= 0 ? trans[c] : Qnil; } else { v = hash ? rb_hash_aref(hash, INT2NUM(c)) : Qnil; } if (!NIL_P(v)) { if (!cflag) { c = NUM2INT(v); tlen = rb_enc_codelen(c, enc); modify = 1; } else { c = last; modify = 1; } } while (t - buf + tlen >= max) { offset = t - buf; max *= 2; REALLOC_N(buf, char, max); t = buf + offset; } if (s != t) rb_enc_mbcput(c, t, enc); s += clen; t += tlen; } if (!STR_EMBED_P(str)) { free(RSTRING(str)->as.heap.ptr); } *t = '\0'; RSTRING(str)->as.heap.ptr = buf; RSTRING(str)->as.heap.len = t - buf; STR_SET_NOEMBED(str); RSTRING(str)->as.heap.aux.capa = max; } if (modify) { rb_enc_associate(str, enc); return str; } return Qnil; } /* * call-seq: * str.tr!(from_str, to_str) => str or nil * * Translates str in place, using the same rules as * String#tr. Returns str, or nil 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. Both * strings may use the c1--c2 notation to denote ranges of characters, and * from_str may start with a ^, which denotes all * characters except those listed. * * "hello".tr('aeiou', '*') #=> "h*ll*" * "hello".tr('^aeiou', '*') #=> "*e**o" * "hello".tr('el', 'ip') #=> "hippo" * "hello".tr('a-y', 'b-z') #=> "ifmmp" */ static VALUE rb_str_tr(VALUE str, VALUE src, VALUE repl) { str = rb_str_dup(str); tr_trans(str, src, repl, 0); return str; } static void tr_setup_table(VALUE str, char stable[256], int first, VALUE *tablep, VALUE *ctablep, rb_encoding *enc) { char buf[256]; struct tr tr; int c; VALUE table = 0, ptable = 0; int i, 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 && RSTRING_PTR(str)[0] == '^') { cflag = 1; tr.p++; } if (first) { for (i=0; i<256; i++) { stable[i] = 1; } } for (i=0; i<256; i++) { buf[i] = cflag; } while ((c = trnext(&tr, enc)) >= 0) { if (c < 256) { buf[c & 0xff] = !cflag; } else { VALUE key = INT2NUM(c); if (!table) { table = rb_hash_new(); if (cflag) { ptable = *ctablep; *ctablep = table; } else { ptable = *tablep; *tablep = table; } } if (!ptable || !NIL_P(rb_hash_aref(ptable, key))) { rb_hash_aset(table, key, Qtrue); } } } for (i=0; i<256; i++) { stable[i] = stable[i] && buf[i]; } } static int tr_find(int c, char table[256], VALUE del, VALUE nodel) { if (c < 256) { return table[c] ? Qtrue : Qfalse; } else { VALUE v = INT2NUM(c); if (!del || NIL_P(rb_hash_lookup(del, v))) { return Qfalse; } if (nodel && NIL_P(rb_hash_lookup(nodel, v))) return Qfalse; return Qtrue; } } /* * call-seq: * str.delete!([other_str]+) => str or nil * * Performs a delete operation in place, returning str, or * nil if str was not modified. */ static VALUE rb_str_delete_bang(int argc, VALUE *argv, VALUE str) { char squeez[256]; rb_encoding *enc = 0; char *s, *send, *t; VALUE del = 0, nodel = 0; int modify = 0; int i; if (argc < 1) { rb_raise(rb_eArgError, "wrong number of arguments"); } for (i=0; i new_str * * Returns a copy of str 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 = rb_str_dup(str); rb_str_delete_bang(argc, argv, str); return str; } /* * call-seq: * str.squeeze!([other_str]*) => str or nil * * Squeezes str in place, returning either str, or * nil if no changes were made. */ static VALUE rb_str_squeeze_bang(int argc, VALUE *argv, VALUE str) { char squeez[256]; rb_encoding *enc = 0; VALUE del = 0, nodel = 0; char *s, *send, *t; int save, modify = 0; int i; if (argc == 0) { enc = STR_ENC_GET(str); } else { for (i=0; i 0 && !tr_find(c, squeez, del, nodel))) { if (t != s) rb_enc_mbcput(c, t, enc); save = c; t += clen; } s += clen; } *t = '\0'; if (t - RSTRING_PTR(str) != RSTRING_LEN(str)) { STR_SET_LEN(str, 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 other_str 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 = rb_str_dup(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 str in place, * returning str, or nil 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 str 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 = rb_str_dup(str); tr_trans(str, src, repl, 1); return str; } /* * call-seq: * str.count([other_str]+) => fixnum * * 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 (^) is * negated. The sequence c1--c2 means all characters between c1 and c2. * * a = "hello world" * a.count "lo" #=> 5 * a.count "lo", "o" #=> 2 * a.count "hello", "^l" #=> 4 * a.count "ej-m" #=> 4 */ static VALUE rb_str_count(int argc, VALUE *argv, VALUE str) { char table[256]; rb_encoding *enc = 0; VALUE del = 0, nodel = 0; char *s, *send; int i; if (argc < 1) { rb_raise(rb_eArgError, "wrong number of arguments"); } for (i=0; i anArray * * Divides str into substrings based on a delimiter, returning an array * of these substrings. * * If pattern is a String, then its contents are used as * the delimiter when splitting str. If pattern is a single * space, str is split on whitespace, with leading whitespace and runs * of contiguous whitespace characters ignored. * * If pattern is a Regexp, str is divided where the * pattern matches. Whenever the pattern matches a zero-length string, * str is split into individual characters. If pattern contains * groups, the respective matches will be returned in the array as well. * * If pattern is omitted, the value of $; is used. If * $; is nil (which is the default), str is * split on whitespace as if ` ' were specified. * * If the limit parameter is omitted, trailing null fields are * suppressed. If limit is a positive number, at most that number of * fields will be returned (if limit is 1, 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. * * " 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", "", ""] */ static VALUE rb_str_split_m(int argc, VALUE *argv, VALUE str) { rb_encoding *enc; VALUE spat; VALUE limit; int awk_split = Qfalse; long beg, end, i = 0; int lim = 0; VALUE result, tmp; 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 rb_ary_new2(0); return rb_ary_new3(1, str); } i = 1; } enc = STR_ENC_GET(str); if (NIL_P(spat)) { if (!NIL_P(rb_fs)) { spat = rb_fs; goto fs_set; } awk_split = Qtrue; } else { fs_set: if (TYPE(spat) == T_STRING && RSTRING_LEN(spat) == 1) { if (RSTRING_PTR(spat)[0] == ' ') { awk_split = Qtrue; } else { spat = rb_reg_regcomp(rb_reg_quote(spat)); } } else { spat = get_pat(spat, 1); } } result = rb_ary_new(); beg = 0; if (awk_split) { char *ptr = RSTRING_PTR(str); char *eptr = RSTRING_END(str); char *bptr = ptr; int skip = 1; int c; end = beg; while (ptr < eptr) { c = rb_enc_codepoint(ptr, eptr, enc); ptr += rb_enc_mbclen(ptr, eptr, enc); if (skip) { if (rb_enc_isspace(c, enc)) { beg = ptr - bptr; } else { end = ptr - bptr; skip = 0; if (!NIL_P(limit) && lim <= i) break; } } else { if (rb_enc_isspace(c, enc)) { rb_ary_push(result, rb_str_subseq(str, beg, end-beg)); skip = 1; beg = ptr - bptr; if (!NIL_P(limit)) ++i; } else { end = ptr - bptr; } } } } else { long start = beg; long idx; int last_null = 0; struct re_registers *regs; while ((end = rb_reg_search(spat, str, start, 0)) >= 0) { regs = RMATCH_REGS(rb_backref_get()); if (start == end && BEG(0) == END(0)) { if (!RSTRING_PTR(str)) { rb_ary_push(result, rb_str_new("", 0)); break; } else if (last_null == 1) { rb_ary_push(result, rb_str_subseq(str, beg, rb_enc_mbclen(RSTRING_PTR(str)+beg, RSTRING_END(str), enc))); beg = start; } else { if (RSTRING_PTR(str)+start == RSTRING_END(str)) start++; else start += rb_enc_mbclen(RSTRING_PTR(str)+start,RSTRING_END(str),enc); last_null = 1; continue; } } else { rb_ary_push(result, rb_str_subseq(str, beg, end-beg)); beg = start = END(0); } last_null = 0; for (idx=1; idx < regs->num_regs; idx++) { if (BEG(idx) == -1) continue; if (BEG(idx) == END(idx)) tmp = rb_str_new5(str, 0, 0); else tmp = rb_str_subseq(str, BEG(idx), END(idx)-BEG(idx)); rb_ary_push(result, tmp); } if (!NIL_P(limit) && lim <= ++i) break; } } if (RSTRING_LEN(str) > 0 && (!NIL_P(limit) || RSTRING_LEN(str) > beg || lim < 0)) { if (RSTRING_LEN(str) == beg) tmp = rb_str_new5(str, 0, 0); else tmp = rb_str_subseq(str, beg, RSTRING_LEN(str)-beg); rb_ary_push(result, tmp); } if (NIL_P(limit) && lim == 0) { while (RARRAY_LEN(result) > 0 && RSTRING_LEN(RARRAY_PTR(result)[RARRAY_LEN(result)-1]) == 0) rb_ary_pop(result); } return result; } VALUE rb_str_split(VALUE str, const char *sep0) { VALUE sep; StringValue(str); sep = rb_str_new2(sep0); return rb_str_split_m(1, &sep, str); } /* * Document-method: lines * call-seq: * str.lines(separator=$/) => anEnumerator * str.lines(separator=$/) {|substr| block } => str * * Returns an enumerator that gives each line in the string. If a block is * given, it iterates over each line in the string. * * "foo\nbar\n".lines.to_a #=> ["foo\n", "bar\n"] * "foo\nb ar".lines.sort #=> ["b ar", "foo\n"] */ /* * Document-method: each_line * call-seq: * str.each_line(separator=$/) {|substr| block } => str * * Splits str using the supplied parameter as the record separator * ($/ by default), passing each substring in turn to the supplied * block. If a zero-length record separator is supplied, the string is split on * \n characters, except that multiple successive newlines are * appended together. * * print "Example one\n" * "hello\nworld".each {|s| p s} * print "Example two\n" * "hello\nworld".each('l') {|s| p s} * print "Example three\n" * "hello\n\n\nworld".each('') {|s| p s} * * produces: * * Example one * "hello\n" * "world" * Example two * "hel" * "l" * "o\nworl" * "d" * Example three * "hello\n\n\n" * "world" */ static VALUE rb_str_each_line(int argc, VALUE *argv, VALUE str) { rb_encoding *enc; VALUE rs; int newline; char *p, *pend, *s, *ptr; long len, rslen; VALUE line; int n; if (rb_scan_args(argc, argv, "01", &rs) == 0) { rs = rb_rs; } RETURN_ENUMERATOR(str, argc, argv); if (NIL_P(rs)) { rb_yield(str); return str; } str = rb_str_new4(str); ptr = p = s = RSTRING_PTR(str); pend = p + RSTRING_LEN(str); len = RSTRING_LEN(str); StringValue(rs); if (rs == rb_default_rs) { enc = rb_enc_get(str); while (p < pend) { char *p0; p = memchr(p, '\n', pend - p); if (!p) break; p0 = rb_enc_left_char_head(s, p, enc); if (!rb_enc_is_newline(p0, pend, enc)) { p++; continue; } p = p0 + rb_enc_mbclen(p0, pend, enc); line = rb_str_new5(str, s, p - s); OBJ_INFECT(line, str); rb_enc_cr_str_copy(line, str); rb_yield(line); str_mod_check(str, ptr, len); s = p; } goto finish; } enc = rb_enc_check(str, rs); rslen = RSTRING_LEN(rs); if (rslen == 0) { newline = '\n'; } else { newline = rb_enc_codepoint(RSTRING_PTR(rs), RSTRING_END(rs), enc); } while (p < pend) { int c = rb_enc_codepoint(p, pend, enc); n = rb_enc_codelen(c, enc); if (rslen == 0 && c == newline) { while (p < pend && rb_enc_codepoint(p, pend, enc) == newline) { p += n; } p -= n; } if (c == newline && (rslen <= 1 || memcmp(RSTRING_PTR(rs), p, rslen) == 0)) { line = rb_str_new5(str, s, p - s + (rslen ? rslen : n)); OBJ_INFECT(line, str); rb_enc_cr_str_copy(line, str); rb_yield(line); str_mod_check(str, ptr, len); s = p + (rslen ? rslen : n); } p += n; } finish: if (s != pend) { line = rb_str_new5(str, s, pend - s); OBJ_INFECT(line, str); rb_enc_cr_str_copy(line, str); rb_yield(line); } return str; } /* * Document-method: bytes * call-seq: * str.bytes => anEnumerator * str.bytes {|fixnum| block } => str * * Returns an enumerator that gives each byte in the string. If a block is * given, it iterates over each byte in the string. * * "hello".bytes.to_a #=> [104, 101, 108, 108, 111] */ /* * Document-method: each_byte * call-seq: * str.each_byte {|fixnum| block } => str * * Passes each byte in str to the given block. * * "hello".each_byte {|c| print c, ' ' } * * produces: * * 104 101 108 108 111 */ static VALUE rb_str_each_byte(VALUE str) { long i; RETURN_ENUMERATOR(str, 0, 0); for (i=0; i anEnumerator * str.chars {|substr| block } => str * * Returns an enumerator that gives each character in the string. * If a block is given, it iterates over each character in the string. * * "foo".chars.to_a #=> ["f","o","o"] */ /* * Document-method: each_char * call-seq: * str.each_char {|cstr| block } => str * * Passes each character in str to the given block. * * "hello".each_char {|c| print c, ' ' } * * produces: * * h e l l o */ static VALUE rb_str_each_char(VALUE str) { int i, len, n; const char *ptr; rb_encoding *enc; RETURN_ENUMERATOR(str, 0, 0); str = rb_str_new4(str); ptr = RSTRING_PTR(str); len = RSTRING_LEN(str); enc = rb_enc_get(str); for (i = 0; i < len; i += n) { n = rb_enc_mbclen(ptr + i, ptr + len, enc); rb_yield(rb_str_subseq(str, i, n)); } return str; } 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, enc); if (!p) return 0; if (p > beg && rb_enc_codepoint(p, end, enc) == '\n') { p2 = rb_enc_prev_char(beg, p, enc); if (p2 && rb_enc_codepoint(p2, end, enc) == '\r') p = p2; } return p - beg; } /* * call-seq: * str.chop! => str or nil * * Processes str as for String#chop, returning str, * or nil if str is the empty string. See also * String#chomp!. */ static VALUE rb_str_chop_bang(VALUE str) { if (RSTRING_LEN(str) > 0) { long len; rb_str_modify(str); len = chopped_length(str); STR_SET_LEN(str, len); RSTRING_PTR(str)[len] = '\0'; return str; } return Qnil; } /* * call-seq: * str.chop => new_str * * Returns a new String with the last character removed. If the * string ends with \r\n, both characters are removed. Applying * chop 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) { VALUE str2 = rb_str_new5(str, RSTRING_PTR(str), chopped_length(str)); rb_enc_cr_str_copy(str2, str); OBJ_INFECT(str2, str); return str2; } /* * call-seq: * str.chomp!(separator=$/) => str or nil * * Modifies str in place as described for String#chomp, * returning str, or nil if no modifications were made. */ static VALUE rb_str_chomp_bang(int argc, VALUE *argv, VALUE str) { rb_encoding *enc; VALUE rs; int newline; char *p, *pp, *e; long len, rslen; if (rb_scan_args(argc, argv, "01", &rs) == 0) { len = RSTRING_LEN(str); if (len == 0) return Qnil; p = RSTRING_PTR(str); rs = rb_rs; if (rs == rb_default_rs) { smart_chomp: rb_str_modify(str); if (RSTRING_PTR(str)[len-1] == '\n') { STR_DEC_LEN(str); if (RSTRING_LEN(str) > 0 && RSTRING_PTR(str)[RSTRING_LEN(str)-1] == '\r') { STR_DEC_LEN(str); } } else if (RSTRING_PTR(str)[len-1] == '\r') { STR_DEC_LEN(str); } else { return Qnil; } RSTRING_PTR(str)[RSTRING_LEN(str)] = '\0'; return str; } } if (NIL_P(rs)) return Qnil; StringValue(rs); enc = rb_enc_check(str, rs); len = RSTRING_LEN(str); if (len == 0) return Qnil; p = RSTRING_PTR(str); rslen = RSTRING_LEN(rs); if (rslen == 0) { while (len>0 && p[len-1] == '\n') { len--; if (len>0 && p[len-1] == '\r') len--; } if (len < RSTRING_LEN(str)) { rb_str_modify(str); STR_SET_LEN(str, len); RSTRING_PTR(str)[len] = '\0'; return str; } return Qnil; } if (rslen > len) return Qnil; newline = RSTRING_PTR(rs)[rslen-1]; if (rslen == 1 && newline == '\n') goto smart_chomp; if (is_broken_string(rs)) { return Qnil; } e = p + len; pp = e - rslen; if (p[len-1] == newline && (rslen <= 1 || memcmp(RSTRING_PTR(rs), pp, rslen) == 0)) { if (rb_enc_left_char_head(p, pp, enc) != pp) return Qnil; rb_str_modify(str); STR_SET_LEN(str, RSTRING_LEN(str) - rslen); RSTRING_PTR(str)[RSTRING_LEN(str)] = '\0'; return str; } return Qnil; } /* * call-seq: * str.chomp(separator=$/) => new_str * * Returns a new String with the given record separator removed * from the end of str (if present). If $/ has not been * changed from the default Ruby record separator, then chomp also * removes carriage return characters (that is it will remove \n, * \r, and \r\n). * * "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" */ static VALUE rb_str_chomp(int argc, VALUE *argv, VALUE str) { str = rb_str_dup(str); rb_str_chomp_bang(argc, argv, str); return str; } /* * call-seq: * str.lstrip! => self or nil * * Removes leading whitespace from str, returning nil if no * change was made. See also String#rstrip! and * String#strip!. * * " hello ".lstrip #=> "hello " * "hello".lstrip! #=> nil */ static VALUE rb_str_lstrip_bang(VALUE str) { rb_encoding *enc; char *s, *t, *e; rb_str_modify(str); enc = STR_ENC_GET(str); s = RSTRING_PTR(str); if (!s || RSTRING_LEN(str) == 0) return Qnil; e = t = RSTRING_END(str); /* remove spaces at head */ while (s < e) { int cc = rb_enc_codepoint(s, e, enc); if (!rb_enc_isspace(cc, enc)) break; s += rb_enc_codelen(cc, enc); } if (s > RSTRING_PTR(str)) { rb_str_modify(str); STR_SET_LEN(str, t-s); memmove(RSTRING_PTR(str), s, RSTRING_LEN(str)); RSTRING_PTR(str)[RSTRING_LEN(str)] = '\0'; return str; } return Qnil; } /* * call-seq: * str.lstrip => new_str * * Returns a copy of str with leading whitespace removed. See also * String#rstrip and String#strip. * * " hello ".lstrip #=> "hello " * "hello".lstrip #=> "hello" */ static VALUE rb_str_lstrip(VALUE str) { str = rb_str_dup(str); rb_str_lstrip_bang(str); return str; } /* * call-seq: * str.rstrip! => self or nil * * Removes trailing whitespace from str, returning nil if * no change was made. See also String#lstrip! and * String#strip!. * * " hello ".rstrip #=> " hello" * "hello".rstrip! #=> nil */ static VALUE rb_str_rstrip_bang(VALUE str) { rb_encoding *enc; char *s, *t, *e; int space_seen = Qfalse; rb_str_modify(str); enc = STR_ENC_GET(str); s = RSTRING_PTR(str); if (!s || RSTRING_LEN(str) == 0) return Qnil; t = e = RSTRING_END(str); while (s < e) { int cc = rb_enc_codepoint(s, e, enc); if (!cc || rb_enc_isspace(cc, enc)) { if (!space_seen) t = s; space_seen = Qtrue; } else { space_seen = Qfalse; } s += rb_enc_codelen(cc, enc); } if (!space_seen) t = s; if (t < e) { rb_str_modify(str); STR_SET_LEN(str, t-RSTRING_PTR(str)); RSTRING_PTR(str)[RSTRING_LEN(str)] = '\0'; return str; } return Qnil; } /* * call-seq: * str.rstrip => new_str * * Returns a copy of str with trailing whitespace removed. See also * String#lstrip and String#strip. * * " hello ".rstrip #=> " hello" * "hello".rstrip #=> "hello" */ static VALUE rb_str_rstrip(VALUE str) { str = rb_str_dup(str); rb_str_rstrip_bang(str); return str; } /* * call-seq: * str.strip! => str or nil * * Removes leading and trailing whitespace from str. Returns * nil if str was not altered. */ static VALUE rb_str_strip_bang(VALUE str) { VALUE l = rb_str_lstrip_bang(str); VALUE r = rb_str_rstrip_bang(str); if (NIL_P(l) && NIL_P(r)) return Qnil; return str; } /* * call-seq: * str.strip => new_str * * Returns a copy of str with leading and trailing whitespace removed. * * " hello ".strip #=> "hello" * "\tgoodbye\r\n".strip #=> "goodbye" */ static VALUE rb_str_strip(VALUE str) { str = rb_str_dup(str); rb_str_strip_bang(str); return str; } static VALUE scan_once(VALUE str, VALUE pat, long *start) { rb_encoding *enc; VALUE result, match; struct re_registers *regs; long i; enc = STR_ENC_GET(str); if (rb_reg_search(pat, str, *start, 0) >= 0) { match = rb_backref_get(); regs = RMATCH_REGS(match); if (BEG(0) == END(0)) { /* * Always consume at least one character of the input string */ if (RSTRING_LEN(str) > END(0)) *start = END(0)+rb_enc_mbclen(RSTRING_PTR(str)+END(0), RSTRING_END(str), enc); else *start = END(0)+1; } else { *start = END(0); } if (regs->num_regs == 1) { return rb_reg_nth_match(0, match); } result = rb_ary_new2(regs->num_regs); for (i=1; i < regs->num_regs; i++) { rb_ary_push(result, rb_reg_nth_match(i, match)); } return result; } return Qnil; } /* * call-seq: * str.scan(pattern) => array * str.scan(pattern) {|match, ...| block } => str * * Both forms iterate through str, 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, $&. 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" * * produces: * * <> <> * rceu lowlr */ static VALUE rb_str_scan(VALUE str, VALUE pat) { VALUE result; long start = 0; VALUE match = Qnil; char *p = RSTRING_PTR(str); long len = RSTRING_LEN(str); pat = get_pat(pat, 1); if (!rb_block_given_p()) { VALUE ary = rb_ary_new(); while (!NIL_P(result = scan_once(str, pat, &start))) { match = rb_backref_get(); rb_ary_push(ary, result); } rb_backref_set(match); return ary; } while (!NIL_P(result = scan_once(str, pat, &start))) { match = rb_backref_get(); rb_match_busy(match); rb_yield(result); str_mod_check(str, p, len); rb_backref_set(match); /* restore $~ value */ } rb_backref_set(match); return str; } /* * call-seq: * str.hex => integer * * Treats leading characters from str as a string of hexadecimal digits * (with an optional sign and an optional 0x) 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, Qfalse); } /* * call-seq: * str.oct => integer * * Treats leading characters of str 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 */ static VALUE rb_str_oct(VALUE str) { return rb_str_to_inum(str, -8, Qfalse); } /* * call-seq: * str.crypt(other_str) => new_str * * Applies a one-way cryptographic hash to str by invoking the standard * library function crypt. The argument is the salt string, which * should be two characters long, each character drawn from * [a-zA-Z0-9./]. */ static VALUE rb_str_crypt(VALUE str, VALUE salt) { extern char *crypt(const char *, const char *); VALUE result; const char *s; StringValue(salt); if (RSTRING_LEN(salt) < 2) rb_raise(rb_eArgError, "salt too short (need >=2 bytes)"); if (RSTRING_PTR(str)) s = RSTRING_PTR(str); else s = ""; result = rb_str_new2(crypt(s, RSTRING_PTR(salt))); OBJ_INFECT(result, str); OBJ_INFECT(result, salt); return result; } /* * call-seq: * str.intern => symbol * str.to_sym => symbol * * Returns the Symbol corresponding to str, creating the * symbol if it did not previously exist. See Symbol#id2name. * * "Koala".intern #=> :Koala * s = 'cat'.to_sym #=> :cat * s == :cat #=> true * s = '@cat'.to_sym #=> :@cat * s == :@cat #=> true * * This can also be used to create symbols that cannot be represented using the * :xxx notation. * * 'cat and dog'.to_sym #=> :"cat and dog" */ VALUE rb_str_intern(VALUE s) { VALUE str = RB_GC_GUARD(s); ID id; if (OBJ_TAINTED(str) && rb_safe_level() >= 1) { rb_raise(rb_eSecurityError, "Insecure: can't intern tainted string"); } id = rb_intern_str(str); return ID2SYM(id); } /* * call-seq: * str.ord => integer * * Return the Integer ordinal of a one-character string. * * "a".ord #=> 97 */ VALUE rb_str_ord(VALUE s) { int c; c = rb_enc_codepoint(RSTRING_PTR(s), RSTRING_END(s), STR_ENC_GET(s)); return INT2NUM(c); } /* * call-seq: * str.sum(n=16) => integer * * Returns a basic n-bit checksum of the characters in str, * where n is the optional Fixnum parameter, defaulting * to 16. The result is simply the sum of the binary value of each character in * str modulo 2n - 1. This is not a particularly good * checksum. */ static VALUE rb_str_sum(int argc, VALUE *argv, VALUE str) { VALUE vbits; int bits; char *ptr, *p, *pend; long len; if (rb_scan_args(argc, argv, "01", &vbits) == 0) { bits = 16; } else bits = NUM2INT(vbits); ptr = p = RSTRING_PTR(str); len = RSTRING_LEN(str); pend = p + len; if (bits >= sizeof(long)*CHAR_BIT) { VALUE sum = INT2FIX(0); while (p < pend) { str_mod_check(str, ptr, len); sum = rb_funcall(sum, '+', 1, INT2FIX((unsigned char)*p)); p++; } if (bits != 0) { VALUE mod; mod = rb_funcall(INT2FIX(1), rb_intern("<<"), 1, INT2FIX(bits)); mod = rb_funcall(mod, '-', 1, INT2FIX(1)); sum = rb_funcall(sum, '&', 1, mod); } return sum; } else { unsigned long sum = 0; while (p < pend) { str_mod_check(str, ptr, len); sum += (unsigned char)*p; p++; } if (bits != 0) { sum &= (((unsigned long)1)<= width) return rb_str_dup(str); n = width - len; llen = (jflag == 'l') ? 0 : ((jflag == 'r') ? n : n/2); rlen = n - llen; res = rb_str_new5(str, 0, RSTRING_LEN(str)+n*flen/fclen+2); p = RSTRING_PTR(res); while (llen) { if (flen <= 1) { *p++ = *f; llen--; } else if (llen > fclen) { memcpy(p,f,flen); p += flen; llen -= fclen; } else { char *fp = str_nth(f, f+flen, llen, enc, singlebyte); n = fp - f; memcpy(p,f,n); p+=n; break; } } memcpy(p, RSTRING_PTR(str), RSTRING_LEN(str)); p+=RSTRING_LEN(str); while (rlen) { if (flen <= 1) { *p++ = *f; rlen--; } else if (rlen > fclen) { memcpy(p,f,flen); p += flen; rlen -= fclen; } else { char *fp = str_nth(f, f+flen, rlen, enc, singlebyte); n = fp - f; memcpy(p,f,n); p+=n; break; } } *p = '\0'; STR_SET_LEN(res, p-RSTRING_PTR(res)); OBJ_INFECT(res, str); if (!NIL_P(pad)) OBJ_INFECT(res, pad); rb_enc_associate(res, enc); return res; } /* * call-seq: * str.ljust(integer, padstr=' ') => new_str * * If integer is greater than the length of str, returns a new * String of length integer with str left justified * and padded with padstr; otherwise, returns str. * * "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 integer is greater than the length of str, returns a new * String of length integer with str right justified * and padded with padstr; otherwise, returns str. * * "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(integer, padstr) => new_str * * If integer is greater than the length of str, returns a new * String of length integer 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] * * Searches the string for sep and returns the part before * it, the sep, and the part after it. If sep is not found, * returns str and two empty strings. * * "hello".partition("l") #=> ["he", "l", "lo"] * "hello".partition("x") #=> ["hello", "", ""] */ static VALUE rb_str_partition(VALUE str, VALUE sep) { long pos; int regex = Qfalse; if (TYPE(sep) == T_REGEXP) { pos = rb_reg_search(sep, str, 0, 0); regex = Qtrue; } else { VALUE tmp; tmp = rb_check_string_type(sep); if (NIL_P(tmp)) { rb_raise(rb_eTypeError, "type mismatch: %s given", rb_obj_classname(sep)); } pos = rb_str_index(str, sep, 0); } if (pos < 0) { failed: return rb_ary_new3(3, str, rb_str_new(0,0),rb_str_new(0,0)); } if (regex) { sep = rb_str_subpat(str, sep, 0); if (pos == 0 && RSTRING_LEN(sep) == 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))); } /* * call-seq: * str.rpartition(sep) => [head, sep, tail] * * Searches sep in the string from the end of the string, and * returns the part before it, the sep, and the part after it. * If sep is not found, returns two empty strings and * str. * * "hello".rpartition("l") #=> ["hel", "l", "o"] * "hello".rpartition("x") #=> ["", "", "hello"] */ static VALUE rb_str_rpartition(VALUE str, VALUE sep) { long pos = RSTRING_LEN(str); int regex = Qfalse; if (TYPE(sep) == T_REGEXP) { pos = rb_reg_search(sep, str, pos, 1); regex = Qtrue; } else { VALUE tmp; tmp = rb_check_string_type(sep); if (NIL_P(tmp)) { rb_raise(rb_eTypeError, "type mismatch: %s given", rb_obj_classname(sep)); } pos = rb_str_sublen(str, pos); pos = rb_str_rindex(str, sep, pos); } if (pos < 0) { return rb_ary_new3(3, rb_str_new(0,0),rb_str_new(0,0), str); } if (regex) { sep = rb_reg_nth_match(0, rb_backref_get()); } 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))); } /* * call-seq: * str.start_with?([prefix]+) => true or false * * Returns true if str starts with the prefix given. */ static VALUE rb_str_start_with(int argc, VALUE *argv, VALUE str) { int i; for (i=0; i true or false * * Returns true if str ends with the suffix given. */ static VALUE rb_str_end_with(int argc, VALUE *argv, VALUE str) { int i; char *p, *s; rb_encoding *enc; for (i=0; i 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)); return str; } /* * call-seq: * str.valid_encoding? => true or false * * Returns true for a string which 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; } /********************************************************************** * Document-class: Symbol * * Symbol objects represent names and some strings * inside the Ruby * interpreter. They are generated using the :name and * :"string" literals * syntax, and by the various to_sym 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 Fred is a constant in * one context, a method in another, and a class in a third, the * Symbol :Fred 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.id #=> 2514190 * $f2.id #=> 2514190 * $f3.id #=> 2514190 * */ /* * call-seq: * sym == obj => true or false * * Equality---If sym and obj are exactly the same * symbol, returns true. Otherwise, compares them * as strings. */ static VALUE sym_equal(VALUE sym1, VALUE sym2) { if (sym1 == sym2) return Qtrue; return Qfalse; } /* * call-seq: * sym.to_i => fixnum * * Returns an integer that is unique for each symbol within a * particular execution of a program. * * :fred.to_i #=> 9809 * "fred".to_sym.to_i #=> 9809 */ static VALUE sym_to_i(VALUE sym) { ID id = SYM2ID(sym); return LONG2FIX(id); } /* * call-seq: * sym.inspect => string * * Returns the representation of sym as a symbol literal. * * :fred.inspect #=> ":fred" */ static VALUE sym_inspect(VALUE sym) { VALUE str, klass = Qundef; ID id = SYM2ID(sym); rb_encoding *enc; sym = rb_id2str(id); enc = STR_ENC_GET(sym); str = rb_enc_str_new(0, RSTRING_LEN(sym)+1, enc); RSTRING_PTR(str)[0] = ':'; memcpy(RSTRING_PTR(str)+1, RSTRING_PTR(sym), RSTRING_LEN(sym)); if (RSTRING_LEN(sym) != strlen(RSTRING_PTR(sym)) || !rb_enc_symname_p(RSTRING_PTR(sym), enc)) { str = rb_str_dump(str); strncpy(RSTRING_PTR(str), ":\"", 2); } if (klass != Qundef) { rb_str_cat2(str, "/"); rb_str_append(str, rb_inspect(klass)); } return str; } /* * call-seq: * sym.id2name => string * sym.to_s => string * * Returns the name or string corresponding to sym. * * :fred.id2name #=> "fred" */ VALUE rb_sym_to_s(VALUE sym) { ID id = SYM2ID(sym); return str_new3(rb_cString, rb_id2str(id)); } /* * call-seq: * sym.to_sym => sym * sym.intern => sym * * In general, to_sym returns the Symbol corresponding * to an object. As sym is already a symbol, self is returned * in this case. */ static VALUE sym_to_sym(VALUE sym) { return sym; } static VALUE sym_call(VALUE args, VALUE sym, int argc, VALUE *argv) { VALUE obj; if (argc < 1) { rb_raise(rb_eArgError, "no receiver given"); } obj = argv[0]; return rb_funcall3(obj, (ID)sym, argc - 1, argv + 1); } /* * call-seq: * sym.to_proc * * Returns a _Proc_ object which respond to the given method by _sym_. * * (1..3).collect(&:to_s) #=> ["1", "2", "3"] */ static VALUE sym_to_proc(VALUE sym) { return rb_proc_new(sym_call, (VALUE)SYM2ID(sym)); } static VALUE sym_succ(VALUE sym) { return rb_str_intern(rb_str_succ(rb_sym_to_s(sym))); } static VALUE sym_cmp(VALUE sym, VALUE other) { if (!SYMBOL_P(other)) { return Qnil; } return rb_str_cmp_m(rb_sym_to_s(sym), rb_sym_to_s(other)); } static VALUE sym_casecmp(VALUE sym, VALUE other) { if (!SYMBOL_P(other)) { return Qnil; } return rb_str_casecmp(rb_sym_to_s(sym), rb_sym_to_s(other)); } static VALUE sym_match(VALUE sym, VALUE other) { return rb_str_match(rb_sym_to_s(sym), other); } static VALUE sym_eqq(VALUE sym, VALUE other) { if (sym == other) return Qtrue; return rb_str_equal(rb_sym_to_s(sym), other); } static VALUE sym_aref(int argc, VALUE *argv, VALUE sym) { return rb_str_aref_m(argc, argv, rb_sym_to_s(sym)); } static VALUE sym_length(VALUE sym) { return rb_str_length(rb_id2str(SYM2ID(sym))); } static VALUE sym_empty(VALUE sym) { return rb_str_empty(rb_id2str(SYM2ID(sym))); } static VALUE sym_upcase(VALUE sym) { return rb_str_intern(rb_str_upcase(rb_id2str(SYM2ID(sym)))); } static VALUE sym_downcase(VALUE sym) { return rb_str_intern(rb_str_downcase(rb_id2str(SYM2ID(sym)))); } static VALUE sym_capitalize(VALUE sym) { return rb_str_intern(rb_str_capitalize(rb_id2str(SYM2ID(sym)))); } static VALUE sym_swapcase(VALUE sym) { return rb_str_intern(rb_str_swapcase(rb_id2str(SYM2ID(sym)))); } static VALUE sym_encoding(VALUE sym) { return rb_obj_encoding(rb_id2str(SYM2ID(sym))); } ID rb_to_id(VALUE name) { VALUE tmp; ID id; switch (TYPE(name)) { default: tmp = rb_check_string_type(name); if (NIL_P(tmp)) { rb_raise(rb_eTypeError, "%s is not a symbol", RSTRING_PTR(rb_inspect(name))); } name = tmp; /* fall through */ case T_STRING: name = rb_str_intern(name); /* fall through */ case T_SYMBOL: return SYM2ID(name); } return id; } /* * 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#[]=. * */ void Init_String(void) { rb_cString = rb_define_class("String", rb_cObject); rb_include_module(rb_cString, rb_mComparable); rb_define_alloc_func(rb_cString, 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, "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, "+", 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, "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, "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, "upcase", rb_str_upcase, 0); rb_define_method(rb_cString, "downcase", rb_str_downcase, 0); rb_define_method(rb_cString, "capitalize", rb_str_capitalize, 0); rb_define_method(rb_cString, "swapcase", rb_str_swapcase, 0); rb_define_method(rb_cString, "upcase!", rb_str_upcase_bang, 0); rb_define_method(rb_cString, "downcase!", rb_str_downcase_bang, 0); rb_define_method(rb_cString, "capitalize!", rb_str_capitalize_bang, 0); rb_define_method(rb_cString, "swapcase!", rb_str_swapcase_bang, 0); 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_each_line, -1); rb_define_method(rb_cString, "bytes", rb_str_each_byte, 0); rb_define_method(rb_cString, "chars", rb_str_each_char, 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, 1); rb_define_method(rb_cString, "<<", rb_str_concat, 1); rb_define_method(rb_cString, "crypt", rb_str_crypt, 1); rb_define_method(rb_cString, "intern", rb_str_intern, 0); rb_define_method(rb_cString, "to_sym", rb_str_intern, 0); 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, "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, "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, "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, "valid_encoding?", rb_str_valid_encoding_p, 0); rb_define_method(rb_cString, "ascii_only?", rb_str_is_ascii_only_p, 0); id_to_s = rb_intern("to_s"); rb_fs = Qnil; rb_define_variable("$;", &rb_fs); rb_define_variable("$-F", &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", rb_sym_all_symbols, 0); /* in parse.y */ rb_define_method(rb_cSymbol, "==", sym_equal, 1); rb_define_method(rb_cSymbol, "to_i", sym_to_i, 0); 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, "intern", sym_to_sym, 0); rb_define_method(rb_cSymbol, "to_sym", sym_to_sym, 0); rb_define_method(rb_cSymbol, "to_proc", 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, "=~", sym_match, 1); rb_define_method(rb_cSymbol, "===", sym_eqq, 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, 1); rb_define_method(rb_cSymbol, "upcase", sym_upcase, 0); rb_define_method(rb_cSymbol, "downcase", sym_downcase, 0); rb_define_method(rb_cSymbol, "capitalize", sym_capitalize, 0); rb_define_method(rb_cSymbol, "swapcase", sym_swapcase, 0); rb_define_method(rb_cSymbol, "encoding", sym_encoding, 0); }