/********************************************************************** array.c - $Author: shyouhei $ $Date: 2009-02-05 00:55:01 +0100 (Thu, 05 Feb 2009) $ created at: Fri Aug 6 09:46:12 JST 1993 Copyright (C) 1993-2003 Yukihiro Matsumoto Copyright (C) 2000 Network Applied Communication Laboratory, Inc. Copyright (C) 2000 Information-technology Promotion Agency, Japan **********************************************************************/ #include "ruby.h" #include "util.h" #include "st.h" VALUE rb_cArray; static ID id_cmp; #define ARY_DEFAULT_SIZE 16 #define ARY_MAX_SIZE (LONG_MAX / sizeof(VALUE)) void rb_mem_clear(mem, size) register VALUE *mem; register long size; { while (size--) { *mem++ = Qnil; } } static inline void memfill(mem, size, val) register VALUE *mem; register long size; register VALUE val; { while (size--) { *mem++ = val; } } #define ARY_TMPLOCK FL_USER1 static inline void rb_ary_modify_check(ary) VALUE ary; { if (OBJ_FROZEN(ary)) rb_error_frozen("array"); if (FL_TEST(ary, ARY_TMPLOCK)) rb_raise(rb_eRuntimeError, "can't modify array during iteration"); if (!OBJ_TAINTED(ary) && rb_safe_level() >= 4) rb_raise(rb_eSecurityError, "Insecure: can't modify array"); } static void rb_ary_modify(ary) VALUE ary; { VALUE *ptr; rb_ary_modify_check(ary); if (FL_TEST(ary, ELTS_SHARED)) { ptr = ALLOC_N(VALUE, RARRAY(ary)->len); FL_UNSET(ary, ELTS_SHARED); RARRAY(ary)->aux.capa = RARRAY(ary)->len; MEMCPY(ptr, RARRAY(ary)->ptr, VALUE, RARRAY(ary)->len); RARRAY(ary)->ptr = ptr; } } VALUE rb_ary_freeze(ary) VALUE ary; { return rb_obj_freeze(ary); } /* * call-seq: * array.frozen? -> true or false * * Return true if this array is frozen (or temporarily frozen * while being sorted). */ static VALUE rb_ary_frozen_p(ary) VALUE ary; { if (OBJ_FROZEN(ary)) return Qtrue; if (FL_TEST(ary, ARY_TMPLOCK)) return Qtrue; return Qfalse; } static VALUE ary_alloc _((VALUE)); static VALUE ary_alloc(klass) VALUE klass; { NEWOBJ(ary, struct RArray); OBJSETUP(ary, klass, T_ARRAY); ary->len = 0; ary->ptr = 0; ary->aux.capa = 0; return (VALUE)ary; } static VALUE ary_new(klass, len) VALUE klass; long len; { VALUE ary = ary_alloc(klass); if (len < 0) { rb_raise(rb_eArgError, "negative array size (or size too big)"); } if (len > ARY_MAX_SIZE) { rb_raise(rb_eArgError, "array size too big"); } if (len == 0) len++; RARRAY(ary)->ptr = ALLOC_N(VALUE, len); RARRAY(ary)->aux.capa = len; return ary; } VALUE rb_ary_new2(len) long len; { return ary_new(rb_cArray, len); } VALUE rb_ary_new() { return rb_ary_new2(ARY_DEFAULT_SIZE); } #ifdef HAVE_STDARG_PROTOTYPES #include #define va_init_list(a,b) va_start(a,b) #else #include #define va_init_list(a,b) va_start(a) #endif VALUE #ifdef HAVE_STDARG_PROTOTYPES rb_ary_new3(long n, ...) #else rb_ary_new3(n, va_alist) long n; va_dcl #endif { va_list ar; VALUE ary; long i; ary = rb_ary_new2(n); va_init_list(ar, n); for (i=0; iptr[i] = va_arg(ar, VALUE); } va_end(ar); RARRAY(ary)->len = n; return ary; } VALUE rb_ary_new4(n, elts) long n; const VALUE *elts; { VALUE ary; ary = rb_ary_new2(n); if (n > 0 && elts) { MEMCPY(RARRAY(ary)->ptr, elts, VALUE, n); } /* This assignment to len will be moved to the above "if" block in Ruby 1.9 */ RARRAY(ary)->len = n; return ary; } VALUE rb_assoc_new(car, cdr) VALUE car, cdr; { VALUE ary; ary = rb_ary_new2(2); RARRAY(ary)->ptr[0] = car; RARRAY(ary)->ptr[1] = cdr; RARRAY(ary)->len = 2; return ary; } static VALUE to_ary(ary) VALUE ary; { return rb_convert_type(ary, T_ARRAY, "Array", "to_ary"); } VALUE rb_check_array_type(ary) VALUE ary; { return rb_check_convert_type(ary, T_ARRAY, "Array", "to_ary"); } static VALUE rb_ary_replace _((VALUE, VALUE)); /* * call-seq: * Array.new(size=0, obj=nil) * Array.new(array) * Array.new(size) {|index| block } * * Returns a new array. In the first form, the new array is * empty. In the second it is created with _size_ copies of _obj_ * (that is, _size_ references to the same * _obj_). The third form creates a copy of the array * passed as a parameter (the array is generated by calling * to_ary on the parameter). In the last form, an array * of the given size is created. Each element in this array is * calculated by passing the element's index to the given block and * storing the return value. * * Array.new * Array.new(2) * Array.new(5, "A") * * # only one copy of the object is created * a = Array.new(2, Hash.new) * a[0]['cat'] = 'feline' * a * a[1]['cat'] = 'Felix' * a * * # here multiple copies are created * a = Array.new(2) { Hash.new } * a[0]['cat'] = 'feline' * a * * squares = Array.new(5) {|i| i*i} * squares * * copy = Array.new(squares) */ static VALUE rb_ary_initialize(argc, argv, ary) int argc; VALUE *argv; VALUE ary; { long len; VALUE size, val; rb_ary_modify(ary); if (rb_scan_args(argc, argv, "02", &size, &val) == 0) { RARRAY(ary)->len = 0; if (rb_block_given_p()) { rb_warning("given block not used"); } return ary; } if (argc == 1 && !FIXNUM_P(size)) { val = rb_check_array_type(size); if (!NIL_P(val)) { rb_ary_replace(ary, val); return ary; } } len = NUM2LONG(size); if (len < 0) { rb_raise(rb_eArgError, "negative array size"); } if (len > ARY_MAX_SIZE) { rb_raise(rb_eArgError, "array size too big"); } if (len > RARRAY(ary)->aux.capa) { REALLOC_N(RARRAY(ary)->ptr, VALUE, len); RARRAY(ary)->aux.capa = len; } if (rb_block_given_p()) { long i; if (argc == 2) { rb_warn("block supersedes default value argument"); } for (i=0; ilen = i + 1; } } else { memfill(RARRAY(ary)->ptr, len, val); RARRAY(ary)->len = len; } return ary; } /* * Returns a new array populated with the given objects. * * Array.[]( 1, 'a', /^A/ ) * Array[ 1, 'a', /^A/ ] * [ 1, 'a', /^A/ ] */ static VALUE rb_ary_s_create(argc, argv, klass) int argc; VALUE *argv; VALUE klass; { VALUE ary = ary_alloc(klass); if (argc > 0) { RARRAY(ary)->ptr = ALLOC_N(VALUE, argc); MEMCPY(RARRAY(ary)->ptr, argv, VALUE, argc); } RARRAY(ary)->len = RARRAY(ary)->aux.capa = argc; return ary; } void rb_ary_store(ary, idx, val) VALUE ary; long idx; VALUE val; { if (idx < 0) { idx += RARRAY(ary)->len; if (idx < 0) { rb_raise(rb_eIndexError, "index %ld out of array", idx - RARRAY(ary)->len); } } else if (idx >= ARY_MAX_SIZE) { rb_raise(rb_eIndexError, "index %ld too big", idx); } rb_ary_modify(ary); if (idx >= RARRAY(ary)->aux.capa) { long new_capa = RARRAY(ary)->aux.capa / 2; if (new_capa < ARY_DEFAULT_SIZE) { new_capa = ARY_DEFAULT_SIZE; } if (new_capa >= ARY_MAX_SIZE - idx) { new_capa = (ARY_MAX_SIZE - idx) / 2; } new_capa += idx; REALLOC_N(RARRAY(ary)->ptr, VALUE, new_capa); RARRAY(ary)->aux.capa = new_capa; } if (idx > RARRAY(ary)->len) { rb_mem_clear(RARRAY(ary)->ptr + RARRAY(ary)->len, idx-RARRAY(ary)->len + 1); } if (idx >= RARRAY(ary)->len) { RARRAY(ary)->len = idx + 1; } RARRAY(ary)->ptr[idx] = val; } /* * call-seq: * array << obj -> array * * Append---Pushes the given object on to the end of this array. This * expression returns the array itself, so several appends * may be chained together. * * [ 1, 2 ] << "c" << "d" << [ 3, 4 ] * #=> [ 1, 2, "c", "d", [ 3, 4 ] ] * */ VALUE rb_ary_push(ary, item) VALUE ary; VALUE item; { rb_ary_store(ary, RARRAY(ary)->len, item); return ary; } /* * call-seq: * array.push(obj, ... ) -> array * * Append---Pushes the given object(s) on to the end of this array. This * expression returns the array itself, so several appends * may be chained together. * * a = [ "a", "b", "c" ] * a.push("d", "e", "f") * #=> ["a", "b", "c", "d", "e", "f"] */ static VALUE rb_ary_push_m(argc, argv, ary) int argc; VALUE *argv; VALUE ary; { while (argc--) { rb_ary_push(ary, *argv++); } return ary; } /* * call-seq: * array.pop -> obj or nil * * Removes the last element from self and returns it, or * nil if the array is empty. * * a = [ "a", "m", "z" ] * a.pop #=> "z" * a #=> ["a", "m"] */ VALUE rb_ary_pop(ary) VALUE ary; { rb_ary_modify_check(ary); if (RARRAY(ary)->len == 0) return Qnil; if (!FL_TEST(ary, ELTS_SHARED) && RARRAY(ary)->len * 2 < RARRAY(ary)->aux.capa && RARRAY(ary)->aux.capa > ARY_DEFAULT_SIZE) { RARRAY(ary)->aux.capa = RARRAY(ary)->len * 2; REALLOC_N(RARRAY(ary)->ptr, VALUE, RARRAY(ary)->aux.capa); } return RARRAY(ary)->ptr[--RARRAY(ary)->len]; } static VALUE ary_make_shared(ary) VALUE ary; { if (!FL_TEST(ary, ELTS_SHARED)) { NEWOBJ(shared, struct RArray); OBJSETUP(shared, rb_cArray, T_ARRAY); shared->len = RARRAY(ary)->len; shared->ptr = RARRAY(ary)->ptr; shared->aux.capa = RARRAY(ary)->aux.capa; RARRAY(ary)->aux.shared = (VALUE)shared; FL_SET(ary, ELTS_SHARED); OBJ_FREEZE(shared); return (VALUE)shared; } else { return RARRAY(ary)->aux.shared; } } /* * call-seq: * array.shift -> obj or nil * * Returns the first element of self and removes it (shifting all * other elements down by one). Returns nil if the array * is empty. * * args = [ "-m", "-q", "filename" ] * args.shift #=> "-m" * args #=> ["-q", "filename"] */ VALUE rb_ary_shift(ary) VALUE ary; { VALUE top; rb_ary_modify_check(ary); if (RARRAY(ary)->len == 0) return Qnil; top = RARRAY(ary)->ptr[0]; if (RARRAY_LEN(ary) < ARY_DEFAULT_SIZE && !FL_TEST(ary, ELTS_SHARED)) { MEMMOVE(RARRAY_PTR(ary), RARRAY_PTR(ary)+1, VALUE, RARRAY_LEN(ary)-1); } else { if (!FL_TEST(ary, ELTS_SHARED)) { RARRAY(ary)->ptr[0] = Qnil; } ary_make_shared(ary); RARRAY(ary)->ptr++; /* shift ptr */ } RARRAY(ary)->len--; return top; } VALUE rb_ary_unshift(ary, item) VALUE ary, item; { rb_ary_modify(ary); if (RARRAY(ary)->len == RARRAY(ary)->aux.capa) { long capa_inc = RARRAY(ary)->aux.capa / 2; if (capa_inc < ARY_DEFAULT_SIZE) { capa_inc = ARY_DEFAULT_SIZE; } RARRAY(ary)->aux.capa += capa_inc; REALLOC_N(RARRAY(ary)->ptr, VALUE, RARRAY(ary)->aux.capa); } /* sliding items */ MEMMOVE(RARRAY(ary)->ptr + 1, RARRAY(ary)->ptr, VALUE, RARRAY(ary)->len); RARRAY(ary)->len++; RARRAY(ary)->ptr[0] = item; return ary; } /* * call-seq: * array.unshift(obj, ...) -> array * * Prepends objects to the front of array. * other elements up one. * * a = [ "b", "c", "d" ] * a.unshift("a") #=> ["a", "b", "c", "d"] * a.unshift(1, 2) #=> [ 1, 2, "a", "b", "c", "d"] */ static VALUE rb_ary_unshift_m(argc, argv, ary) int argc; VALUE *argv; VALUE ary; { long len = RARRAY(ary)->len; if (argc == 0) return ary; /* make rooms by setting the last item */ rb_ary_store(ary, len + argc - 1, Qnil); /* sliding items */ MEMMOVE(RARRAY(ary)->ptr + argc, RARRAY(ary)->ptr, VALUE, len); MEMCPY(RARRAY(ary)->ptr, argv, VALUE, argc); return ary; } /* faster version - use this if you don't need to treat negative offset */ static inline VALUE rb_ary_elt(ary, offset) VALUE ary; long offset; { if (RARRAY(ary)->len == 0) return Qnil; if (offset < 0 || RARRAY(ary)->len <= offset) { return Qnil; } return RARRAY(ary)->ptr[offset]; } VALUE rb_ary_entry(ary, offset) VALUE ary; long offset; { if (offset < 0) { offset += RARRAY(ary)->len; } return rb_ary_elt(ary, offset); } static VALUE rb_ary_subseq(ary, beg, len) VALUE ary; long beg, len; { VALUE klass, ary2, shared; VALUE *ptr; if (beg > RARRAY(ary)->len) return Qnil; if (beg < 0 || len < 0) return Qnil; if (RARRAY(ary)->len < len || RARRAY(ary)->len < beg + len) { len = RARRAY(ary)->len - beg; if (len < 0) len = 0; } klass = rb_obj_class(ary); if (len == 0) return ary_new(klass, 0); shared = ary_make_shared(ary); ptr = RARRAY(ary)->ptr; ary2 = ary_alloc(klass); RARRAY(ary2)->ptr = ptr + beg; RARRAY(ary2)->len = len; RARRAY(ary2)->aux.shared = shared; FL_SET(ary2, ELTS_SHARED); return ary2; } /* * call-seq: * array[index] -> obj or nil * array[start, length] -> an_array or nil * array[range] -> an_array or nil * array.slice(index) -> obj or nil * array.slice(start, length) -> an_array or nil * array.slice(range) -> an_array or nil * * Element Reference---Returns the element at _index_, * or returns a subarray starting at _start_ and * continuing for _length_ elements, or returns a subarray * specified by _range_. * Negative indices count backward from the end of the * array (-1 is the last element). Returns nil if the index * (or starting index) are out of range. * * a = [ "a", "b", "c", "d", "e" ] * a[2] + a[0] + a[1] #=> "cab" * a[6] #=> nil * a[1, 2] #=> [ "b", "c" ] * a[1..3] #=> [ "b", "c", "d" ] * a[4..7] #=> [ "e" ] * a[6..10] #=> nil * a[-3, 3] #=> [ "c", "d", "e" ] * # special cases * a[5] #=> nil * a[5, 1] #=> [] * a[5..10] #=> [] * */ VALUE rb_ary_aref(argc, argv, ary) int argc; VALUE *argv; VALUE ary; { VALUE arg; long beg, len; if (argc == 2) { if (SYMBOL_P(argv[0])) { rb_raise(rb_eTypeError, "Symbol as array index"); } beg = NUM2LONG(argv[0]); len = NUM2LONG(argv[1]); if (beg < 0) { beg += RARRAY(ary)->len; } return rb_ary_subseq(ary, beg, len); } if (argc != 1) { rb_scan_args(argc, argv, "11", 0, 0); } arg = argv[0]; /* special case - speeding up */ if (FIXNUM_P(arg)) { return rb_ary_entry(ary, FIX2LONG(arg)); } if (SYMBOL_P(arg)) { rb_raise(rb_eTypeError, "Symbol as array index"); } /* check if idx is Range */ switch (rb_range_beg_len(arg, &beg, &len, RARRAY(ary)->len, 0)) { case Qfalse: break; case Qnil: return Qnil; default: return rb_ary_subseq(ary, beg, len); } return rb_ary_entry(ary, NUM2LONG(arg)); } /* * call-seq: * array.at(index) -> obj or nil * * Returns the element at _index_. A * negative index counts from the end of _self_. Returns +nil+ * if the index is out of range. See also Array#[]. * (Array#at is slightly faster than Array#[], * as it does not accept ranges and so on.) * * a = [ "a", "b", "c", "d", "e" ] * a.at(0) #=> "a" * a.at(-1) #=> "e" */ static VALUE rb_ary_at(ary, pos) VALUE ary, pos; { return rb_ary_entry(ary, NUM2LONG(pos)); } /* * call-seq: * array.first -> obj or nil * array.first(n) -> an_array * * Returns the first element, or the first +n+ elements, of the array. * If the array is empty, the first form returns nil, and the * second form returns an empty array. * * a = [ "q", "r", "s", "t" ] * a.first #=> "q" * a.first(1) #=> ["q"] * a.first(3) #=> ["q", "r", "s"] */ static VALUE rb_ary_first(argc, argv, ary) int argc; VALUE *argv; VALUE ary; { if (argc == 0) { if (RARRAY(ary)->len == 0) return Qnil; return RARRAY(ary)->ptr[0]; } else { VALUE nv, result; long n, i; rb_scan_args(argc, argv, "01", &nv); n = NUM2LONG(nv); if (n > RARRAY(ary)->len) n = RARRAY(ary)->len; result = rb_ary_new2(n); for (i=0; iptr[i]); } return result; } } /* * call-seq: * array.last -> obj or nil * array.last(n) -> an_array * * Returns the last element(s) of self. If the array is empty, * the first form returns nil. * * [ "w", "x", "y", "z" ].last #=> "z" */ static VALUE rb_ary_last(argc, argv, ary) int argc; VALUE *argv; VALUE ary; { if (argc == 0) { if (RARRAY(ary)->len == 0) return Qnil; return RARRAY(ary)->ptr[RARRAY(ary)->len-1]; } else { VALUE nv, result; long n, i; rb_scan_args(argc, argv, "01", &nv); n = NUM2LONG(nv); if (n > RARRAY(ary)->len) n = RARRAY(ary)->len; result = rb_ary_new2(n); for (i=RARRAY(ary)->len-n; n--; i++) { rb_ary_push(result, RARRAY(ary)->ptr[i]); } return result; } } /* * call-seq: * array.fetch(index) -> obj * array.fetch(index, default ) -> obj * array.fetch(index) {|index| block } -> obj * * Tries to return the element at position index. If the index * lies outside the array, the first form throws an * IndexError exception, the second form returns * default, and the third form returns the value of invoking * the block, passing in the index. Negative values of index * count from the end of the array. * * a = [ 11, 22, 33, 44 ] * a.fetch(1) #=> 22 * a.fetch(-1) #=> 44 * a.fetch(4, 'cat') #=> "cat" * a.fetch(4) { |i| i*i } #=> 16 */ static VALUE rb_ary_fetch(argc, argv, ary) int argc; VALUE *argv; VALUE ary; { VALUE pos, ifnone; long block_given; long idx; rb_scan_args(argc, argv, "11", &pos, &ifnone); block_given = rb_block_given_p(); if (block_given && argc == 2) { rb_warn("block supersedes default value argument"); } idx = NUM2LONG(pos); if (idx < 0) { idx += RARRAY(ary)->len; } if (idx < 0 || RARRAY(ary)->len <= idx) { if (block_given) return rb_yield(pos); if (argc == 1) { rb_raise(rb_eIndexError, "index %ld out of array", idx); } return ifnone; } return RARRAY(ary)->ptr[idx]; } /* * call-seq: * array.index(obj) -> int or nil * * Returns the index of the first object in self such that is * == to obj. Returns nil if * no match is found. * * a = [ "a", "b", "c" ] * a.index("b") #=> 1 * a.index("z") #=> nil */ static VALUE rb_ary_index(ary, val) VALUE ary; VALUE val; { long i; for (i=0; ilen; i++) { if (rb_equal(RARRAY(ary)->ptr[i], val)) return LONG2NUM(i); } return Qnil; } /* * call-seq: * array.rindex(obj) -> int or nil * * Returns the index of the last object in array * == to obj. Returns nil if * no match is found. * * a = [ "a", "b", "b", "b", "c" ] * a.rindex("b") #=> 3 * a.rindex("z") #=> nil */ static VALUE rb_ary_rindex(ary, val) VALUE ary; VALUE val; { long i = RARRAY(ary)->len; while (i--) { if (i > RARRAY(ary)->len) { i = RARRAY(ary)->len; continue; } if (rb_equal(RARRAY(ary)->ptr[i], val)) return LONG2NUM(i); } return Qnil; } /* * call-seq: * array.indexes( i1, i2, ... iN ) -> an_array * array.indices( i1, i2, ... iN ) -> an_array * * Deprecated; use Array#values_at. */ static VALUE rb_ary_indexes(argc, argv, ary) int argc; VALUE *argv; VALUE ary; { VALUE new_ary; long i; rb_warn("Array#%s is deprecated; use Array#values_at", rb_id2name(rb_frame_last_func())); new_ary = rb_ary_new2(argc); for (i=0; ilen; if (beg < 0) { beg -= RARRAY(ary)->len; rb_raise(rb_eIndexError, "index %ld out of array", beg); } } if (RARRAY(ary)->len < len || RARRAY(ary)->len < beg + len) { len = RARRAY(ary)->len - beg; } if (NIL_P(rpl)) { rlen = 0; } else { rpl = rb_ary_to_ary(rpl); rlen = RARRAY(rpl)->len; } rb_ary_modify(ary); if (beg >= RARRAY(ary)->len) { if (beg > ARY_MAX_SIZE - rlen) { rb_raise(rb_eIndexError, "index %ld too big", beg); } len = beg + rlen; if (len >= RARRAY(ary)->aux.capa) { REALLOC_N(RARRAY(ary)->ptr, VALUE, len); RARRAY(ary)->aux.capa = len; } rb_mem_clear(RARRAY(ary)->ptr + RARRAY(ary)->len, beg - RARRAY(ary)->len); if (rlen > 0) { MEMCPY(RARRAY(ary)->ptr + beg, RARRAY(rpl)->ptr, VALUE, rlen); } RARRAY(ary)->len = len; } else { long alen; if (beg + len > RARRAY(ary)->len) { len = RARRAY(ary)->len - beg; } alen = RARRAY(ary)->len + rlen - len; if (alen >= RARRAY(ary)->aux.capa) { REALLOC_N(RARRAY(ary)->ptr, VALUE, alen); RARRAY(ary)->aux.capa = alen; } if (len != rlen) { MEMMOVE(RARRAY(ary)->ptr + beg + rlen, RARRAY(ary)->ptr + beg + len, VALUE, RARRAY(ary)->len - (beg + len)); RARRAY(ary)->len = alen; } if (rlen > 0) { MEMMOVE(RARRAY(ary)->ptr + beg, RARRAY(rpl)->ptr, VALUE, rlen); } } } /* * call-seq: * array[index] = obj -> obj * array[start, length] = obj or an_array or nil -> obj or an_array or nil * array[range] = obj or an_array or nil -> obj or an_array or nil * * Element Assignment---Sets the element at _index_, * or replaces a subarray starting at _start_ and * continuing for _length_ elements, or replaces a subarray * specified by _range_. If indices are greater than * the current capacity of the array, the array grows * automatically. A negative indices will count backward * from the end of the array. Inserts elements if _length_ is * zero. If +nil+ is used in the second and third form, * deletes elements from _self_. An +IndexError+ is raised if a * negative index points past the beginning of the array. See also * Array#push, and Array#unshift. * * a = Array.new * a[4] = "4"; #=> [nil, nil, nil, nil, "4"] * a[0, 3] = [ 'a', 'b', 'c' ] #=> ["a", "b", "c", nil, "4"] * a[1..2] = [ 1, 2 ] #=> ["a", 1, 2, nil, "4"] * a[0, 2] = "?" #=> ["?", 2, nil, "4"] * a[0..2] = "A" #=> ["A", "4"] * a[-1] = "Z" #=> ["A", "Z"] * a[1..-1] = nil #=> ["A"] */ static VALUE rb_ary_aset(argc, argv, ary) int argc; VALUE *argv; VALUE ary; { long offset, beg, len; if (argc == 3) { if (SYMBOL_P(argv[0])) { rb_raise(rb_eTypeError, "Symbol as array index"); } if (SYMBOL_P(argv[1])) { rb_raise(rb_eTypeError, "Symbol as subarray length"); } rb_ary_splice(ary, 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); } if (FIXNUM_P(argv[0])) { offset = FIX2LONG(argv[0]); goto fixnum; } if (SYMBOL_P(argv[0])) { rb_raise(rb_eTypeError, "Symbol as array index"); } if (rb_range_beg_len(argv[0], &beg, &len, RARRAY(ary)->len, 1)) { /* check if idx is Range */ rb_ary_splice(ary, beg, len, argv[1]); return argv[1]; } offset = NUM2LONG(argv[0]); fixnum: rb_ary_store(ary, offset, argv[1]); return argv[1]; } /* * call-seq: * array.insert(index, obj...) -> array * * Inserts the given values before the element with the given index * (which may be negative). * * a = %w{ a b c d } * a.insert(2, 99) #=> ["a", "b", 99, "c", "d"] * a.insert(-2, 1, 2, 3) #=> ["a", "b", 99, "c", 1, 2, 3, "d"] */ static VALUE rb_ary_insert(argc, argv, ary) int argc; VALUE *argv; VALUE ary; { long pos; if (argc == 1) return ary; if (argc < 1) { rb_raise(rb_eArgError, "wrong number of arguments (at least 1)"); } pos = NUM2LONG(argv[0]); if (pos == -1) { pos = RARRAY(ary)->len; } if (pos < 0) { pos++; } rb_ary_splice(ary, pos, 0, rb_ary_new4(argc - 1, argv + 1)); return ary; } /* * call-seq: * array.each {|item| block } -> array * * Calls block once for each element in self, passing that * element as a parameter. * * a = [ "a", "b", "c" ] * a.each {|x| print x, " -- " } * * produces: * * a -- b -- c -- */ VALUE rb_ary_each(ary) VALUE ary; { long i; for (i=0; ilen; i++) { rb_yield(RARRAY(ary)->ptr[i]); } return ary; } /* * call-seq: * array.each_index {|index| block } -> array * * Same as Array#each, but passes the index of the element * instead of the element itself. * * a = [ "a", "b", "c" ] * a.each_index {|x| print x, " -- " } * * produces: * * 0 -- 1 -- 2 -- */ static VALUE rb_ary_each_index(ary) VALUE ary; { long i; for (i=0; ilen; i++) { rb_yield(LONG2NUM(i)); } return ary; } /* * call-seq: * array.reverse_each {|item| block } * * Same as Array#each, but traverses self in reverse * order. * * a = [ "a", "b", "c" ] * a.reverse_each {|x| print x, " " } * * produces: * * c b a */ static VALUE rb_ary_reverse_each(ary) VALUE ary; { long len = RARRAY(ary)->len; while (len--) { rb_yield(RARRAY(ary)->ptr[len]); if (RARRAY(ary)->len < len) { len = RARRAY(ary)->len; } } return ary; } /* * call-seq: * array.length -> int * * Returns the number of elements in self. May be zero. * * [ 1, 2, 3, 4, 5 ].length #=> 5 */ static VALUE rb_ary_length(ary) VALUE ary; { return LONG2NUM(RARRAY(ary)->len); } /* * call-seq: * array.empty? -> true or false * * Returns true if self array contains no elements. * * [].empty? #=> true */ static VALUE rb_ary_empty_p(ary) VALUE ary; { if (RARRAY(ary)->len == 0) return Qtrue; return Qfalse; } VALUE rb_ary_dup(ary) VALUE ary; { VALUE dup = rb_ary_new2(RARRAY(ary)->len); DUPSETUP(dup, ary); MEMCPY(RARRAY(dup)->ptr, RARRAY(ary)->ptr, VALUE, RARRAY(ary)->len); RARRAY(dup)->len = RARRAY(ary)->len; return dup; } extern VALUE rb_output_fs; static VALUE inspect_join(ary, arg) VALUE ary; VALUE *arg; { return rb_ary_join(arg[0], arg[1]); } VALUE rb_ary_join(ary, sep) VALUE ary, sep; { long len = 1, i; int taint = Qfalse; VALUE result, tmp; if (RARRAY(ary)->len == 0) return rb_str_new(0, 0); if (OBJ_TAINTED(ary) || OBJ_TAINTED(sep)) taint = Qtrue; for (i=0; ilen; i++) { tmp = rb_check_string_type(RARRAY(ary)->ptr[i]); len += NIL_P(tmp) ? 10 : RSTRING(tmp)->len; } if (!NIL_P(sep)) { StringValue(sep); len += RSTRING(sep)->len * (RARRAY(ary)->len - 1); } result = rb_str_buf_new(len); for (i=0; ilen; i++) { tmp = RARRAY(ary)->ptr[i]; switch (TYPE(tmp)) { case T_STRING: break; case T_ARRAY: if (tmp == ary || rb_inspecting_p(tmp)) { tmp = rb_str_new2("[...]"); } else { VALUE args[2]; args[0] = tmp; args[1] = sep; tmp = rb_protect_inspect(inspect_join, ary, (VALUE)args); } break; default: tmp = rb_obj_as_string(tmp); } if (i > 0 && !NIL_P(sep)) rb_str_buf_append(result, sep); rb_str_buf_append(result, tmp); if (OBJ_TAINTED(tmp)) taint = Qtrue; } if (taint) OBJ_TAINT(result); return result; } /* * call-seq: * array.join(sep=$,) -> str * * Returns a string created by converting each element of the array to * a string, separated by sep. * * [ "a", "b", "c" ].join #=> "abc" * [ "a", "b", "c" ].join("-") #=> "a-b-c" */ static VALUE rb_ary_join_m(argc, argv, ary) int argc; VALUE *argv; VALUE ary; { VALUE sep; rb_scan_args(argc, argv, "01", &sep); if (NIL_P(sep)) sep = rb_output_fs; return rb_ary_join(ary, sep); } /* * call-seq: * array.to_s -> string * * Returns _self_.join. * * [ "a", "e", "i", "o" ].to_s #=> "aeio" * */ VALUE rb_ary_to_s(ary) VALUE ary; { if (RARRAY(ary)->len == 0) return rb_str_new(0, 0); return rb_ary_join(ary, rb_output_fs); } static ID inspect_key; struct inspect_arg { VALUE (*func)(); VALUE arg1, arg2; }; static VALUE inspect_call(arg) struct inspect_arg *arg; { return (*arg->func)(arg->arg1, arg->arg2); } static VALUE get_inspect_tbl(create) int create; { VALUE inspect_tbl = rb_thread_local_aref(rb_thread_current(), inspect_key); if (NIL_P(inspect_tbl)) { if (create) { tbl_init: inspect_tbl = rb_ary_new(); rb_thread_local_aset(rb_thread_current(), inspect_key, inspect_tbl); } } else if (TYPE(inspect_tbl) != T_ARRAY) { rb_warn("invalid inspect_tbl value"); if (create) goto tbl_init; rb_thread_local_aset(rb_thread_current(), inspect_key, Qnil); return Qnil; } return inspect_tbl; } static VALUE inspect_ensure(obj) VALUE obj; { VALUE inspect_tbl; inspect_tbl = get_inspect_tbl(Qfalse); if (!NIL_P(inspect_tbl)) { rb_ary_pop(inspect_tbl); } return 0; } VALUE rb_protect_inspect(func, obj, arg) VALUE (*func)(ANYARGS); VALUE obj, arg; { struct inspect_arg iarg; VALUE inspect_tbl; VALUE id; inspect_tbl = get_inspect_tbl(Qtrue); id = rb_obj_id(obj); if (rb_ary_includes(inspect_tbl, id)) { return (*func)(obj, arg); } rb_ary_push(inspect_tbl, id); iarg.func = func; iarg.arg1 = obj; iarg.arg2 = arg; return rb_ensure(inspect_call, (VALUE)&iarg, inspect_ensure, obj); } VALUE rb_inspecting_p(obj) VALUE obj; { VALUE inspect_tbl; inspect_tbl = get_inspect_tbl(Qfalse); if (NIL_P(inspect_tbl)) return Qfalse; return rb_ary_includes(inspect_tbl, rb_obj_id(obj)); } static VALUE inspect_ary(ary) VALUE ary; { int tainted = OBJ_TAINTED(ary); long i; VALUE s, str; str = rb_str_buf_new2("["); for (i=0; ilen; i++) { s = rb_inspect(RARRAY(ary)->ptr[i]); if (OBJ_TAINTED(s)) tainted = Qtrue; if (i > 0) rb_str_buf_cat2(str, ", "); rb_str_buf_append(str, s); } rb_str_buf_cat2(str, "]"); if (tainted) OBJ_TAINT(str); return str; } /* * call-seq: * array.inspect -> string * * Create a printable version of array. */ static VALUE rb_ary_inspect(ary) VALUE ary; { if (RARRAY(ary)->len == 0) return rb_str_new2("[]"); if (rb_inspecting_p(ary)) return rb_str_new2("[...]"); return rb_protect_inspect(inspect_ary, ary, 0); } /* * call-seq: * array.to_a -> array * * Returns _self_. If called on a subclass of Array, converts * the receiver to an Array object. */ static VALUE rb_ary_to_a(ary) VALUE ary; { if (rb_obj_class(ary) != rb_cArray) { VALUE dup = rb_ary_new2(RARRAY(ary)->len); rb_ary_replace(dup, ary); return dup; } return ary; } /* * call-seq: * array.to_ary -> array * * Returns _self_. */ static VALUE rb_ary_to_ary_m(ary) VALUE ary; { return ary; } VALUE rb_ary_reverse(ary) VALUE ary; { VALUE *p1, *p2; VALUE tmp; rb_ary_modify(ary); if (RARRAY(ary)->len > 1) { p1 = RARRAY(ary)->ptr; p2 = p1 + RARRAY(ary)->len - 1; /* points last item */ while (p1 < p2) { tmp = *p1; *p1++ = *p2; *p2-- = tmp; } } return ary; } /* * call-seq: * array.reverse! -> array * * Reverses _self_ in place. * * a = [ "a", "b", "c" ] * a.reverse! #=> ["c", "b", "a"] * a #=> ["c", "b", "a"] */ static VALUE rb_ary_reverse_bang(ary) VALUE ary; { return rb_ary_reverse(ary); } /* * call-seq: * array.reverse -> an_array * * Returns a new array containing self's elements in reverse order. * * [ "a", "b", "c" ].reverse #=> ["c", "b", "a"] * [ 1 ].reverse #=> [1] */ static VALUE rb_ary_reverse_m(ary) VALUE ary; { return rb_ary_reverse(rb_ary_dup(ary)); } struct ary_sort_data { VALUE ary; VALUE *ptr; long len; }; static void ary_sort_check(data) struct ary_sort_data *data; { if (RARRAY(data->ary)->ptr != data->ptr || RARRAY(data->ary)->len != data->len) { rb_raise(rb_eArgError, "array modified during sort"); } } static int sort_1(a, b, data) VALUE *a, *b; struct ary_sort_data *data; { VALUE retval = rb_yield_values(2, *a, *b); int n; n = rb_cmpint(retval, *a, *b); ary_sort_check(data); return n; } static int sort_2(ap, bp, data) VALUE *ap, *bp; struct ary_sort_data *data; { VALUE retval; VALUE a = *ap, b = *bp; int n; if (FIXNUM_P(a) && FIXNUM_P(b)) { if ((long)a > (long)b) return 1; if ((long)a < (long)b) return -1; return 0; } if (TYPE(a) == T_STRING) { if (TYPE(b) == T_STRING) return rb_str_cmp(a, b); } retval = rb_funcall(a, id_cmp, 1, b); n = rb_cmpint(retval, a, b); ary_sort_check(data); return n; } static VALUE sort_internal(ary) VALUE ary; { struct ary_sort_data data; data.ary = ary; data.ptr = RARRAY(ary)->ptr; data.len = RARRAY(ary)->len; qsort(RARRAY(ary)->ptr, RARRAY(ary)->len, sizeof(VALUE), rb_block_given_p()?sort_1:sort_2, &data); return ary; } static VALUE sort_unlock(ary) VALUE ary; { FL_UNSET(ary, ARY_TMPLOCK); return ary; } /* * call-seq: * array.sort! -> array * array.sort! {| a,b | block } -> array * * Sorts _self_. Comparisons for * the sort will be done using the <=> operator or using * an optional code block. The block implements a comparison between * a and b, returning -1, 0, or +1. See also * Enumerable#sort_by. * * a = [ "d", "a", "e", "c", "b" ] * a.sort #=> ["a", "b", "c", "d", "e"] * a.sort {|x,y| y <=> x } #=> ["e", "d", "c", "b", "a"] */ VALUE rb_ary_sort_bang(ary) VALUE ary; { rb_ary_modify(ary); if (RARRAY(ary)->len > 1) { FL_SET(ary, ARY_TMPLOCK); /* prohibit modification during sort */ rb_ensure(sort_internal, ary, sort_unlock, ary); } return ary; } /* * call-seq: * array.sort -> an_array * array.sort {| a,b | block } -> an_array * * Returns a new array created by sorting self. Comparisons for * the sort will be done using the <=> operator or using * an optional code block. The block implements a comparison between * a and b, returning -1, 0, or +1. See also * Enumerable#sort_by. * * a = [ "d", "a", "e", "c", "b" ] * a.sort #=> ["a", "b", "c", "d", "e"] * a.sort {|x,y| y <=> x } #=> ["e", "d", "c", "b", "a"] */ VALUE rb_ary_sort(ary) VALUE ary; { ary = rb_ary_dup(ary); rb_ary_sort_bang(ary); return ary; } /* * call-seq: * array.collect {|item| block } -> an_array * array.map {|item| block } -> an_array * * Invokes block once for each element of self. Creates a * new array containing the values returned by the block. * See also Enumerable#collect. * * a = [ "a", "b", "c", "d" ] * a.collect {|x| x + "!" } #=> ["a!", "b!", "c!", "d!"] * a #=> ["a", "b", "c", "d"] */ static VALUE rb_ary_collect(ary) VALUE ary; { long i; VALUE collect; if (!rb_block_given_p()) { return rb_ary_new4(RARRAY(ary)->len, RARRAY(ary)->ptr); } collect = rb_ary_new2(RARRAY(ary)->len); for (i = 0; i < RARRAY(ary)->len; i++) { rb_ary_push(collect, rb_yield(RARRAY(ary)->ptr[i])); } return collect; } /* * call-seq: * array.collect! {|item| block } -> array * array.map! {|item| block } -> array * * Invokes the block once for each element of _self_, replacing the * element with the value returned by _block_. * See also Enumerable#collect. * * a = [ "a", "b", "c", "d" ] * a.collect! {|x| x + "!" } * a #=> [ "a!", "b!", "c!", "d!" ] */ static VALUE rb_ary_collect_bang(ary) VALUE ary; { long i; rb_ary_modify(ary); for (i = 0; i < RARRAY(ary)->len; i++) { rb_ary_store(ary, i, rb_yield(RARRAY(ary)->ptr[i])); } return ary; } VALUE rb_values_at(obj, olen, argc, argv, func) VALUE obj; long olen; int argc; VALUE *argv; VALUE (*func) _((VALUE,long)); { VALUE result = rb_ary_new2(argc); long beg, len, i, j; for (i=0; i an_array * * Returns an array containing the elements in * _self_ corresponding to the given selector(s). The selectors * may be either integer indices or ranges. * See also Array#select. * * a = %w{ a b c d e f } * a.values_at(1, 3, 5) * a.values_at(1, 3, 5, 7) * a.values_at(-1, -3, -5, -7) * a.values_at(1..3, 2...5) */ static VALUE rb_ary_values_at(argc, argv, ary) int argc; VALUE *argv; VALUE ary; { return rb_values_at(ary, RARRAY(ary)->len, argc, argv, rb_ary_entry); } /* * call-seq: * array.select {|item| block } -> an_array * * Invokes the block passing in successive elements from array, * returning an array containing those elements for which the block * returns a true value (equivalent to Enumerable#select). * * a = %w{ a b c d e f } * a.select {|v| v =~ /[aeiou]/} #=> ["a", "e"] */ static VALUE rb_ary_select(ary) VALUE ary; { VALUE result; long i; result = rb_ary_new2(RARRAY(ary)->len); for (i = 0; i < RARRAY(ary)->len; i++) { if (RTEST(rb_yield(RARRAY(ary)->ptr[i]))) { rb_ary_push(result, rb_ary_elt(ary, i)); } } return result; } /* * call-seq: * array.delete(obj) -> obj or nil * array.delete(obj) { block } -> obj or nil * * Deletes items from self that are equal to obj. If * the item is not found, returns nil. If the optional * code block is given, returns the result of block if the item * is not found. * * a = [ "a", "b", "b", "b", "c" ] * a.delete("b") #=> "b" * a #=> ["a", "c"] * a.delete("z") #=> nil * a.delete("z") { "not found" } #=> "not found" */ VALUE rb_ary_delete(ary, item) VALUE ary; VALUE item; { long i1, i2; for (i1 = i2 = 0; i1 < RARRAY(ary)->len; i1++) { VALUE e = RARRAY(ary)->ptr[i1]; if (rb_equal(e, item)) continue; if (i1 != i2) { rb_ary_store(ary, i2, e); } i2++; } if (RARRAY(ary)->len == i2) { if (rb_block_given_p()) { return rb_yield(item); } return Qnil; } rb_ary_modify(ary); if (RARRAY(ary)->len > i2) { RARRAY(ary)->len = i2; if (i2 * 2 < RARRAY(ary)->aux.capa && RARRAY(ary)->aux.capa > ARY_DEFAULT_SIZE) { REALLOC_N(RARRAY(ary)->ptr, VALUE, i2 * 2); RARRAY(ary)->aux.capa = i2 * 2; } } return item; } VALUE rb_ary_delete_at(ary, pos) VALUE ary; long pos; { long i, len = RARRAY(ary)->len; VALUE del; if (pos >= len) return Qnil; if (pos < 0) { pos += len; if (pos < 0) return Qnil; } rb_ary_modify(ary); del = RARRAY(ary)->ptr[pos]; for (i = pos + 1; i < len; i++, pos++) { RARRAY(ary)->ptr[pos] = RARRAY(ary)->ptr[i]; } RARRAY(ary)->len = pos; return del; } /* * call-seq: * array.delete_at(index) -> obj or nil * * Deletes the element at the specified index, returning that element, * or nil if the index is out of range. See also * Array#slice!. * * a = %w( ant bat cat dog ) * a.delete_at(2) #=> "cat" * a #=> ["ant", "bat", "dog"] * a.delete_at(99) #=> nil */ static VALUE rb_ary_delete_at_m(ary, pos) VALUE ary, pos; { return rb_ary_delete_at(ary, NUM2LONG(pos)); } /* * call-seq: * array.slice!(index) -> obj or nil * array.slice!(start, length) -> sub_array or nil * array.slice!(range) -> sub_array or nil * * Deletes the element(s) given by an index (optionally with a length) * or by a range. Returns the deleted object, subarray, or * nil if the index is out of range. Equivalent to: * * def slice!(*args) * result = self[*args] * self[*args] = nil * result * end * * a = [ "a", "b", "c" ] * a.slice!(1) #=> "b" * a #=> ["a", "c"] * a.slice!(-1) #=> "c" * a #=> ["a"] * a.slice!(100) #=> nil * a #=> ["a"] */ static VALUE rb_ary_slice_bang(argc, argv, ary) int argc; VALUE *argv; VALUE ary; { VALUE arg1, arg2; long pos, len; if (rb_scan_args(argc, argv, "11", &arg1, &arg2) == 2) { pos = NUM2LONG(arg1); len = NUM2LONG(arg2); delete_pos_len: if (pos < 0) { pos = RARRAY(ary)->len + pos; } arg2 = rb_ary_subseq(ary, pos, len); rb_ary_splice(ary, pos, len, Qnil); /* Qnil/rb_ary_new2(0) */ return arg2; } if (!FIXNUM_P(arg1) && rb_range_beg_len(arg1, &pos, &len, RARRAY(ary)->len, 1)) { goto delete_pos_len; } return rb_ary_delete_at(ary, NUM2LONG(arg1)); } /* * call-seq: * array.reject! {|item| block } -> array or nil * * Equivalent to Array#delete_if, deleting elements from * _self_ for which the block evaluates to true, but returns * nil if no changes were made. Also see * Enumerable#reject. */ static VALUE rb_ary_reject_bang(ary) VALUE ary; { long i1, i2; rb_ary_modify(ary); for (i1 = i2 = 0; i1 < RARRAY(ary)->len; i1++) { VALUE v = RARRAY(ary)->ptr[i1]; if (RTEST(rb_yield(v))) continue; if (i1 != i2) { rb_ary_store(ary, i2, v); } i2++; } if (RARRAY(ary)->len == i2) return Qnil; if (i2 < RARRAY(ary)->len) RARRAY(ary)->len = i2; return ary; } /* * call-seq: * array.reject {|item| block } -> an_array * * Returns a new array containing the items in _self_ * for which the block is not true. */ static VALUE rb_ary_reject(ary) VALUE ary; { ary = rb_ary_dup(ary); rb_ary_reject_bang(ary); return ary; } /* * call-seq: * array.delete_if {|item| block } -> array * * Deletes every element of self for which block evaluates * to true. * * a = [ "a", "b", "c" ] * a.delete_if {|x| x >= "b" } #=> ["a"] */ static VALUE rb_ary_delete_if(ary) VALUE ary; { rb_ary_reject_bang(ary); return ary; } /* * call-seq: * array.zip(arg, ...) -> an_array * array.zip(arg, ...) {| arr | block } -> nil * * Converts any arguments to arrays, then merges elements of * self with corresponding elements from each argument. This * generates a sequence of self.size n-element * arrays, where n is one more that the count of arguments. If * the size of any argument is less than enumObj.size, * nil values are supplied. If a block given, it is * invoked for each output array, otherwise an array of arrays is * returned. * * a = [ 4, 5, 6 ] * b = [ 7, 8, 9 ] * * [1,2,3].zip(a, b) #=> [[1, 4, 7], [2, 5, 8], [3, 6, 9]] * [1,2].zip(a,b) #=> [[1, 4, 7], [2, 5, 8]] * a.zip([1,2],[8]) #=> [[4,1,8], [5,2,nil], [6,nil,nil]] */ static VALUE rb_ary_zip(argc, argv, ary) int argc; VALUE *argv; VALUE ary; { int i, j; long len; VALUE result; for (i=0; ilen; i++) { VALUE tmp = rb_ary_new2(argc+1); rb_ary_push(tmp, rb_ary_elt(ary, i)); for (j=0; jlen; result = rb_ary_new2(len); for (i=0; i an_array * * Assumes that self is an array of arrays and transposes the * rows and columns. * * a = [[1,2], [3,4], [5,6]] * a.transpose #=> [[1, 3, 5], [2, 4, 6]] */ static VALUE rb_ary_transpose(ary) VALUE ary; { long elen = -1, alen, i, j; VALUE tmp, result = 0; alen = RARRAY(ary)->len; if (alen == 0) return rb_ary_dup(ary); for (i=0; ilen; result = rb_ary_new2(elen); for (j=0; jlen) { rb_raise(rb_eIndexError, "element size differs (%d should be %d)", RARRAY(tmp)->len, elen); } for (j=0; j array * * Replaces the contents of self with the contents of * other_array, truncating or expanding if necessary. * * a = [ "a", "b", "c", "d", "e" ] * a.replace([ "x", "y", "z" ]) #=> ["x", "y", "z"] * a #=> ["x", "y", "z"] */ static VALUE rb_ary_replace(copy, orig) VALUE copy, orig; { VALUE shared; rb_ary_modify(copy); orig = to_ary(orig); if (copy == orig) return copy; shared = ary_make_shared(orig); if (RARRAY(copy)->ptr && !FL_TEST(copy, ELTS_SHARED)) free(RARRAY(copy)->ptr); RARRAY(copy)->ptr = RARRAY(orig)->ptr; RARRAY(copy)->len = RARRAY(orig)->len; RARRAY(copy)->aux.shared = shared; FL_SET(copy, ELTS_SHARED); return copy; } /* * call-seq: * array.clear -> array * * Removes all elements from _self_. * * a = [ "a", "b", "c", "d", "e" ] * a.clear #=> [ ] */ VALUE rb_ary_clear(ary) VALUE ary; { rb_ary_modify(ary); RARRAY(ary)->len = 0; if (ARY_DEFAULT_SIZE * 2 < RARRAY(ary)->aux.capa) { REALLOC_N(RARRAY(ary)->ptr, VALUE, ARY_DEFAULT_SIZE * 2); RARRAY(ary)->aux.capa = ARY_DEFAULT_SIZE * 2; } return ary; } /* * call-seq: * array.fill(obj) -> array * array.fill(obj, start [, length]) -> array * array.fill(obj, range ) -> array * array.fill {|index| block } -> array * array.fill(start [, length] ) {|index| block } -> array * array.fill(range) {|index| block } -> array * * The first three forms set the selected elements of self (which * may be the entire array) to obj. A start of * nil is equivalent to zero. A length of * nil is equivalent to self.length. The last three * forms fill the array with the value of the block. The block is * passed the absolute index of each element to be filled. * * a = [ "a", "b", "c", "d" ] * a.fill("x") #=> ["x", "x", "x", "x"] * a.fill("z", 2, 2) #=> ["x", "x", "z", "z"] * a.fill("y", 0..1) #=> ["y", "y", "z", "z"] * a.fill {|i| i*i} #=> [0, 1, 4, 9] * a.fill(-2) {|i| i*i*i} #=> [0, 1, 8, 27] */ static VALUE rb_ary_fill(argc, argv, ary) int argc; VALUE *argv; VALUE ary; { VALUE item, arg1, arg2; long beg = 0, end = 0, len = 0; VALUE *p, *pend; int block_p = Qfalse; if (rb_block_given_p()) { block_p = Qtrue; rb_scan_args(argc, argv, "02", &arg1, &arg2); argc += 1; /* hackish */ } else { rb_scan_args(argc, argv, "12", &item, &arg1, &arg2); } switch (argc) { case 1: beg = 0; len = RARRAY(ary)->len; break; case 2: if (rb_range_beg_len(arg1, &beg, &len, RARRAY(ary)->len, 1)) { break; } /* fall through */ case 3: beg = NIL_P(arg1) ? 0 : NUM2LONG(arg1); if (beg < 0) { beg = RARRAY(ary)->len + beg; if (beg < 0) beg = 0; } len = NIL_P(arg2) ? RARRAY(ary)->len - beg : NUM2LONG(arg2); break; } rb_ary_modify(ary); if (len < 0) { return ary; } if (beg >= ARY_MAX_SIZE || len > ARY_MAX_SIZE - beg) { rb_raise(rb_eArgError, "argument too big"); } end = beg + len; if (end > RARRAY(ary)->len) { if (end >= RARRAY(ary)->aux.capa) { REALLOC_N(RARRAY(ary)->ptr, VALUE, end); RARRAY(ary)->aux.capa = end; } rb_mem_clear(RARRAY(ary)->ptr + RARRAY(ary)->len, end - RARRAY(ary)->len); RARRAY(ary)->len = end; } if (block_p) { VALUE v; long i; for (i=beg; i=RARRAY(ary)->len) break; RARRAY(ary)->ptr[i] = v; } } else { p = RARRAY(ary)->ptr + beg; pend = p + len; while (p < pend) { *p++ = item; } } return ary; } /* * call-seq: * array + other_array -> an_array * * Concatenation---Returns a new array built by concatenating the * two arrays together to produce a third array. * * [ 1, 2, 3 ] + [ 4, 5 ] #=> [ 1, 2, 3, 4, 5 ] */ VALUE rb_ary_plus(x, y) VALUE x, y; { VALUE z; long len; y = to_ary(y); len = RARRAY(x)->len + RARRAY(y)->len; z = rb_ary_new2(len); MEMCPY(RARRAY(z)->ptr, RARRAY(x)->ptr, VALUE, RARRAY(x)->len); MEMCPY(RARRAY(z)->ptr + RARRAY(x)->len, RARRAY(y)->ptr, VALUE, RARRAY(y)->len); RARRAY(z)->len = len; return z; } /* * call-seq: * array.concat(other_array) -> array * * Appends the elements in other_array to _self_. * * [ "a", "b" ].concat( ["c", "d"] ) #=> [ "a", "b", "c", "d" ] */ VALUE rb_ary_concat(x, y) VALUE x, y; { y = to_ary(y); if (RARRAY(y)->len > 0) { rb_ary_splice(x, RARRAY(x)->len, 0, y); } return x; } /* * call-seq: * array * int -> an_array * array * str -> a_string * * Repetition---With a String argument, equivalent to * self.join(str). Otherwise, returns a new array * built by concatenating the _int_ copies of _self_. * * * [ 1, 2, 3 ] * 3 #=> [ 1, 2, 3, 1, 2, 3, 1, 2, 3 ] * [ 1, 2, 3 ] * "," #=> "1,2,3" * */ static VALUE rb_ary_times(ary, times) VALUE ary, times; { VALUE ary2, tmp; long i, len; tmp = rb_check_string_type(times); if (!NIL_P(tmp)) { return rb_ary_join(ary, tmp); } len = NUM2LONG(times); if (len == 0) return ary_new(rb_obj_class(ary), 0); if (len < 0) { rb_raise(rb_eArgError, "negative argument"); } if (ARY_MAX_SIZE/len < RARRAY(ary)->len) { rb_raise(rb_eArgError, "argument too big"); } len *= RARRAY(ary)->len; ary2 = ary_new(rb_obj_class(ary), len); RARRAY(ary2)->len = len; for (i=0; ilen) { MEMCPY(RARRAY(ary2)->ptr+i, RARRAY(ary)->ptr, VALUE, RARRAY(ary)->len); } OBJ_INFECT(ary2, ary); return ary2; } /* * call-seq: * array.assoc(obj) -> an_array or nil * * Searches through an array whose elements are also arrays * comparing _obj_ with the first element of each contained array * using obj.==. * Returns the first contained array that matches (that * is, the first associated array), * or +nil+ if no match is found. * See also Array#rassoc. * * s1 = [ "colors", "red", "blue", "green" ] * s2 = [ "letters", "a", "b", "c" ] * s3 = "foo" * a = [ s1, s2, s3 ] * a.assoc("letters") #=> [ "letters", "a", "b", "c" ] * a.assoc("foo") #=> nil */ VALUE rb_ary_assoc(ary, key) VALUE ary, key; { long i; VALUE v; for (i = 0; i < RARRAY(ary)->len; ++i) { v = RARRAY(ary)->ptr[i]; if (TYPE(v) == T_ARRAY && RARRAY(v)->len > 0 && rb_equal(RARRAY(v)->ptr[0], key)) return v; } return Qnil; } /* * call-seq: * array.rassoc(key) -> an_array or nil * * Searches through the array whose elements are also arrays. Compares * key with the second element of each contained array using * ==. Returns the first contained array that matches. See * also Array#assoc. * * a = [ [ 1, "one"], [2, "two"], [3, "three"], ["ii", "two"] ] * a.rassoc("two") #=> [2, "two"] * a.rassoc("four") #=> nil */ VALUE rb_ary_rassoc(ary, value) VALUE ary, value; { long i; VALUE v; for (i = 0; i < RARRAY(ary)->len; ++i) { v = RARRAY(ary)->ptr[i]; if (TYPE(v) == T_ARRAY && RARRAY(v)->len > 1 && rb_equal(RARRAY(v)->ptr[1], value)) return v; } return Qnil; } static VALUE recursive_equal(ary1, ary2) VALUE ary1, ary2; { long i; for (i=0; ilen; i++) { if (!rb_equal(rb_ary_elt(ary1, i), rb_ary_elt(ary2, i))) return Qfalse; } return Qtrue; } /* * call-seq: * array == other_array -> bool * * Equality---Two arrays are equal if they contain the same number * of elements and if each element is equal to (according to * Object.==) the corresponding element in the other array. * * [ "a", "c" ] == [ "a", "c", 7 ] #=> false * [ "a", "c", 7 ] == [ "a", "c", 7 ] #=> true * [ "a", "c", 7 ] == [ "a", "d", "f" ] #=> false * */ static VALUE rb_ary_equal(ary1, ary2) VALUE ary1, ary2; { if (ary1 == ary2) return Qtrue; if (TYPE(ary2) != T_ARRAY) { if (!rb_respond_to(ary2, rb_intern("to_ary"))) { return Qfalse; } return rb_equal(ary2, ary1); } if (RARRAY(ary1)->len != RARRAY(ary2)->len) return Qfalse; if (rb_inspecting_p(ary1)) return Qfalse; return rb_protect_inspect(recursive_equal, ary1, ary2); } static VALUE recursive_eql(ary1, ary2) VALUE ary1, ary2; { long i; for (i=0; ilen; i++) { if (!rb_eql(rb_ary_elt(ary1, i), rb_ary_elt(ary2, i))) return Qfalse; } return Qtrue; } /* * call-seq: * array.eql?(other) -> true or false * * Returns true if _array_ and _other_ are the same object, * or are both arrays with the same content. */ static VALUE rb_ary_eql(ary1, ary2) VALUE ary1, ary2; { if (ary1 == ary2) return Qtrue; if (TYPE(ary2) != T_ARRAY) return Qfalse; if (RARRAY(ary1)->len != RARRAY(ary2)->len) return Qfalse; if (rb_inspecting_p(ary1)) return Qfalse; return rb_protect_inspect(recursive_eql, ary1, ary2); } static VALUE recursive_hash _((VALUE ary)); static VALUE recursive_hash(ary) VALUE ary; { long i, h; VALUE n; h = RARRAY(ary)->len; for (i=0; ilen; i++) { h = (h << 1) | (h<0 ? 1 : 0); n = rb_hash(RARRAY(ary)->ptr[i]); h ^= NUM2LONG(n); } return LONG2FIX(h); } /* * call-seq: * array.hash -> fixnum * * Compute a hash-code for this array. Two arrays with the same content * will have the same hash code (and will compare using eql?). */ static VALUE rb_ary_hash(ary) VALUE ary; { if (rb_inspecting_p(ary)) { return LONG2FIX(0); } return rb_protect_inspect(recursive_hash, ary, 0); } /* * call-seq: * array.include?(obj) -> true or false * * Returns true if the given object is present in * self (that is, if any object == anObject), * false otherwise. * * a = [ "a", "b", "c" ] * a.include?("b") #=> true * a.include?("z") #=> false */ VALUE rb_ary_includes(ary, item) VALUE ary; VALUE item; { long i; for (i=0; ilen; i++) { if (rb_equal(RARRAY(ary)->ptr[i], item)) { return Qtrue; } } return Qfalse; } VALUE recursive_cmp(ary1, ary2) VALUE ary1, ary2; { long i, len; len = RARRAY(ary1)->len; if (len > RARRAY(ary2)->len) { len = RARRAY(ary2)->len; } for (i=0; i other_array -> -1, 0, +1 * * Comparison---Returns an integer (-1, 0, * or +1) if this array is less than, equal to, or greater than * other_array. Each object in each array is compared * (using <=>). If any value isn't * equal, then that inequality is the return value. If all the * values found are equal, then the return is based on a * comparison of the array lengths. Thus, two arrays are * ``equal'' according to Array#<=> if and only if they have * the same length and the value of each element is equal to the * value of the corresponding element in the other array. * * [ "a", "a", "c" ] <=> [ "a", "b", "c" ] #=> -1 * [ 1, 2, 3, 4, 5, 6 ] <=> [ 1, 2 ] #=> +1 * */ VALUE rb_ary_cmp(ary1, ary2) VALUE ary1, ary2; { long len; VALUE v; ary2 = to_ary(ary2); if (ary1 == ary2) return INT2FIX(0); if (rb_inspecting_p(ary1)) return INT2FIX(0); v = rb_protect_inspect(recursive_cmp, ary1, ary2); if (v != Qundef) return v; len = RARRAY(ary1)->len - RARRAY(ary2)->len; if (len == 0) return INT2FIX(0); if (len > 0) return INT2FIX(1); return INT2FIX(-1); } static VALUE ary_make_hash(ary1, ary2) VALUE ary1, ary2; { VALUE hash = rb_hash_new(); long i; for (i=0; ilen; i++) { rb_hash_aset(hash, RARRAY(ary1)->ptr[i], Qtrue); } if (ary2) { for (i=0; ilen; i++) { rb_hash_aset(hash, RARRAY(ary2)->ptr[i], Qtrue); } } return hash; } /* * call-seq: * array - other_array -> an_array * * Array Difference---Returns a new array that is a copy of * the original array, removing any items that also appear in * other_array. (If you need set-like behavior, see the * library class Set.) * * [ 1, 1, 2, 2, 3, 3, 4, 5 ] - [ 1, 2, 4 ] #=> [ 3, 3, 5 ] */ static VALUE rb_ary_diff(ary1, ary2) VALUE ary1, ary2; { VALUE ary3; volatile VALUE hash; long i; hash = ary_make_hash(to_ary(ary2), 0); ary3 = rb_ary_new(); for (i=0; ilen; i++) { if (st_lookup(RHASH(hash)->tbl, RARRAY(ary1)->ptr[i], 0)) continue; rb_ary_push(ary3, rb_ary_elt(ary1, i)); } return ary3; } /* * call-seq: * array & other_array * * Set Intersection---Returns a new array * containing elements common to the two arrays, with no duplicates. * * [ 1, 1, 3, 5 ] & [ 1, 2, 3 ] #=> [ 1, 3 ] */ static VALUE rb_ary_and(ary1, ary2) VALUE ary1, ary2; { VALUE hash, ary3, v, vv; long i; ary2 = to_ary(ary2); ary3 = rb_ary_new2(RARRAY(ary1)->len < RARRAY(ary2)->len ? RARRAY(ary1)->len : RARRAY(ary2)->len); hash = ary_make_hash(ary2, 0); for (i=0; ilen; i++) { v = vv = rb_ary_elt(ary1, i); if (st_delete(RHASH(hash)->tbl, (st_data_t*)&vv, 0)) { rb_ary_push(ary3, v); } } return ary3; } /* * call-seq: * array | other_array -> an_array * * Set Union---Returns a new array by joining this array with * other_array, removing duplicates. * * [ "a", "b", "c" ] | [ "c", "d", "a" ] * #=> [ "a", "b", "c", "d" ] */ static VALUE rb_ary_or(ary1, ary2) VALUE ary1, ary2; { VALUE hash, ary3; VALUE v, vv; long i; ary2 = to_ary(ary2); ary3 = rb_ary_new2(RARRAY(ary1)->len+RARRAY(ary2)->len); hash = ary_make_hash(ary1, ary2); for (i=0; ilen; i++) { v = vv = rb_ary_elt(ary1, i); if (st_delete(RHASH(hash)->tbl, (st_data_t*)&vv, 0)) { rb_ary_push(ary3, v); } } for (i=0; ilen; i++) { v = vv = rb_ary_elt(ary2, i); if (st_delete(RHASH(hash)->tbl, (st_data_t*)&vv, 0)) { rb_ary_push(ary3, v); } } return ary3; } /* * call-seq: * array.uniq! -> array or nil * * Removes duplicate elements from _self_. * Returns nil if no changes are made (that is, no * duplicates are found). * * a = [ "a", "a", "b", "b", "c" ] * a.uniq! #=> ["a", "b", "c"] * b = [ "a", "b", "c" ] * b.uniq! #=> nil */ static VALUE rb_ary_uniq_bang(ary) VALUE ary; { VALUE hash, v, vv; long i, j; hash = ary_make_hash(ary, 0); if (RARRAY(ary)->len == RHASH(hash)->tbl->num_entries) { return Qnil; } for (i=j=0; ilen; i++) { v = vv = rb_ary_elt(ary, i); if (st_delete(RHASH(hash)->tbl, (st_data_t*)&vv, 0)) { rb_ary_store(ary, j++, v); } } RARRAY(ary)->len = j; return ary; } /* * call-seq: * array.uniq -> an_array * * Returns a new array by removing duplicate values in self. * * a = [ "a", "a", "b", "b", "c" ] * a.uniq #=> ["a", "b", "c"] */ static VALUE rb_ary_uniq(ary) VALUE ary; { ary = rb_ary_dup(ary); rb_ary_uniq_bang(ary); return ary; } /* * call-seq: * array.compact! -> array or nil * * Removes +nil+ elements from array. * Returns +nil+ if no changes were made. * * [ "a", nil, "b", nil, "c" ].compact! #=> [ "a", "b", "c" ] * [ "a", "b", "c" ].compact! #=> nil */ static VALUE rb_ary_compact_bang(ary) VALUE ary; { VALUE *p, *t, *end; rb_ary_modify(ary); p = t = RARRAY(ary)->ptr; end = p + RARRAY(ary)->len; while (t < end) { if (NIL_P(*t)) t++; else *p++ = *t++; } if (RARRAY(ary)->len == (p - RARRAY(ary)->ptr)) { return Qnil; } RARRAY(ary)->len = RARRAY(ary)->aux.capa = (p - RARRAY(ary)->ptr); REALLOC_N(RARRAY(ary)->ptr, VALUE, RARRAY(ary)->len); return ary; } /* * call-seq: * array.compact -> an_array * * Returns a copy of _self_ with all +nil+ elements removed. * * [ "a", nil, "b", nil, "c", nil ].compact * #=> [ "a", "b", "c" ] */ static VALUE rb_ary_compact(ary) VALUE ary; { ary = rb_ary_dup(ary); rb_ary_compact_bang(ary); return ary; } /* * call-seq: * array.nitems -> int * * Returns the number of non-nil elements in _self_. * May be zero. * * [ 1, nil, 3, nil, 5 ].nitems #=> 3 */ static VALUE rb_ary_nitems(ary) VALUE ary; { long n = 0; VALUE *p, *pend; p = RARRAY(ary)->ptr; pend = p + RARRAY(ary)->len; while (p < pend) { if (!NIL_P(*p)) n++; p++; } return LONG2NUM(n); } static long flatten(ary, idx, ary2, memo) VALUE ary; long idx; VALUE ary2, memo; { VALUE id; long i = idx; long n, lim = idx + RARRAY(ary2)->len; id = rb_obj_id(ary2); if (rb_ary_includes(memo, id)) { rb_raise(rb_eArgError, "tried to flatten recursive array"); } rb_ary_push(memo, id); rb_ary_splice(ary, idx, 1, ary2); while (i < lim) { VALUE tmp; tmp = rb_check_array_type(rb_ary_elt(ary, i)); if (!NIL_P(tmp)) { n = flatten(ary, i, tmp, memo); i += n; lim += n; } i++; } rb_ary_pop(memo); return lim - idx - 1; /* returns number of increased items */ } /* * call-seq: * array.flatten! -> array or nil * * Flattens _self_ in place. * Returns nil if no modifications were made (i.e., * array contains no subarrays.) * * a = [ 1, 2, [3, [4, 5] ] ] * a.flatten! #=> [1, 2, 3, 4, 5] * a.flatten! #=> nil * a #=> [1, 2, 3, 4, 5] */ static VALUE rb_ary_flatten_bang(ary) VALUE ary; { long i = 0; int mod = 0; VALUE memo = Qnil; while (ilen) { VALUE ary2 = RARRAY(ary)->ptr[i]; VALUE tmp; tmp = rb_check_array_type(ary2); if (!NIL_P(tmp)) { if (NIL_P(memo)) { memo = rb_ary_new(); } i += flatten(ary, i, tmp, memo); mod = 1; } i++; } if (mod == 0) return Qnil; return ary; } /* * call-seq: * array.flatten -> an_array * * Returns a new array that is a one-dimensional flattening of this * array (recursively). That is, for every element that is an array, * extract its elements into the new array. * * s = [ 1, 2, 3 ] #=> [1, 2, 3] * t = [ 4, 5, 6, [7, 8] ] #=> [4, 5, 6, [7, 8]] * a = [ s, t, 9, 10 ] #=> [[1, 2, 3], [4, 5, 6, [7, 8]], 9, 10] * a.flatten #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10 */ static VALUE rb_ary_flatten(ary) VALUE ary; { ary = rb_ary_dup(ary); rb_ary_flatten_bang(ary); return ary; } /* Arrays are ordered, integer-indexed collections of any object. * Array indexing starts at 0, as in C or Java. A negative index is * assumed to be relative to the end of the array---that is, an index of -1 * indicates the last element of the array, -2 is the next to last * element in the array, and so on. */ void Init_Array() { rb_cArray = rb_define_class("Array", rb_cObject); rb_include_module(rb_cArray, rb_mEnumerable); rb_define_alloc_func(rb_cArray, ary_alloc); rb_define_singleton_method(rb_cArray, "[]", rb_ary_s_create, -1); rb_define_method(rb_cArray, "initialize", rb_ary_initialize, -1); rb_define_method(rb_cArray, "initialize_copy", rb_ary_replace, 1); rb_define_method(rb_cArray, "to_s", rb_ary_to_s, 0); rb_define_method(rb_cArray, "inspect", rb_ary_inspect, 0); rb_define_method(rb_cArray, "to_a", rb_ary_to_a, 0); rb_define_method(rb_cArray, "to_ary", rb_ary_to_ary_m, 0); rb_define_method(rb_cArray, "frozen?", rb_ary_frozen_p, 0); rb_define_method(rb_cArray, "==", rb_ary_equal, 1); rb_define_method(rb_cArray, "eql?", rb_ary_eql, 1); rb_define_method(rb_cArray, "hash", rb_ary_hash, 0); rb_define_method(rb_cArray, "[]", rb_ary_aref, -1); rb_define_method(rb_cArray, "[]=", rb_ary_aset, -1); rb_define_method(rb_cArray, "at", rb_ary_at, 1); rb_define_method(rb_cArray, "fetch", rb_ary_fetch, -1); rb_define_method(rb_cArray, "first", rb_ary_first, -1); rb_define_method(rb_cArray, "last", rb_ary_last, -1); rb_define_method(rb_cArray, "concat", rb_ary_concat, 1); rb_define_method(rb_cArray, "<<", rb_ary_push, 1); rb_define_method(rb_cArray, "push", rb_ary_push_m, -1); rb_define_method(rb_cArray, "pop", rb_ary_pop, 0); rb_define_method(rb_cArray, "shift", rb_ary_shift, 0); rb_define_method(rb_cArray, "unshift", rb_ary_unshift_m, -1); rb_define_method(rb_cArray, "insert", rb_ary_insert, -1); rb_define_method(rb_cArray, "each", rb_ary_each, 0); rb_define_method(rb_cArray, "each_index", rb_ary_each_index, 0); rb_define_method(rb_cArray, "reverse_each", rb_ary_reverse_each, 0); rb_define_method(rb_cArray, "length", rb_ary_length, 0); rb_define_alias(rb_cArray, "size", "length"); rb_define_method(rb_cArray, "empty?", rb_ary_empty_p, 0); rb_define_method(rb_cArray, "index", rb_ary_index, 1); rb_define_method(rb_cArray, "rindex", rb_ary_rindex, 1); rb_define_method(rb_cArray, "indexes", rb_ary_indexes, -1); rb_define_method(rb_cArray, "indices", rb_ary_indexes, -1); rb_define_method(rb_cArray, "join", rb_ary_join_m, -1); rb_define_method(rb_cArray, "reverse", rb_ary_reverse_m, 0); rb_define_method(rb_cArray, "reverse!", rb_ary_reverse_bang, 0); rb_define_method(rb_cArray, "sort", rb_ary_sort, 0); rb_define_method(rb_cArray, "sort!", rb_ary_sort_bang, 0); rb_define_method(rb_cArray, "collect", rb_ary_collect, 0); rb_define_method(rb_cArray, "collect!", rb_ary_collect_bang, 0); rb_define_method(rb_cArray, "map", rb_ary_collect, 0); rb_define_method(rb_cArray, "map!", rb_ary_collect_bang, 0); rb_define_method(rb_cArray, "select", rb_ary_select, 0); rb_define_method(rb_cArray, "values_at", rb_ary_values_at, -1); rb_define_method(rb_cArray, "delete", rb_ary_delete, 1); rb_define_method(rb_cArray, "delete_at", rb_ary_delete_at_m, 1); rb_define_method(rb_cArray, "delete_if", rb_ary_delete_if, 0); rb_define_method(rb_cArray, "reject", rb_ary_reject, 0); rb_define_method(rb_cArray, "reject!", rb_ary_reject_bang, 0); rb_define_method(rb_cArray, "zip", rb_ary_zip, -1); rb_define_method(rb_cArray, "transpose", rb_ary_transpose, 0); rb_define_method(rb_cArray, "replace", rb_ary_replace, 1); rb_define_method(rb_cArray, "clear", rb_ary_clear, 0); rb_define_method(rb_cArray, "fill", rb_ary_fill, -1); rb_define_method(rb_cArray, "include?", rb_ary_includes, 1); rb_define_method(rb_cArray, "<=>", rb_ary_cmp, 1); rb_define_method(rb_cArray, "slice", rb_ary_aref, -1); rb_define_method(rb_cArray, "slice!", rb_ary_slice_bang, -1); rb_define_method(rb_cArray, "assoc", rb_ary_assoc, 1); rb_define_method(rb_cArray, "rassoc", rb_ary_rassoc, 1); rb_define_method(rb_cArray, "+", rb_ary_plus, 1); rb_define_method(rb_cArray, "*", rb_ary_times, 1); rb_define_method(rb_cArray, "-", rb_ary_diff, 1); rb_define_method(rb_cArray, "&", rb_ary_and, 1); rb_define_method(rb_cArray, "|", rb_ary_or, 1); rb_define_method(rb_cArray, "uniq", rb_ary_uniq, 0); rb_define_method(rb_cArray, "uniq!", rb_ary_uniq_bang, 0); rb_define_method(rb_cArray, "compact", rb_ary_compact, 0); rb_define_method(rb_cArray, "compact!", rb_ary_compact_bang, 0); rb_define_method(rb_cArray, "flatten", rb_ary_flatten, 0); rb_define_method(rb_cArray, "flatten!", rb_ary_flatten_bang, 0); rb_define_method(rb_cArray, "nitems", rb_ary_nitems, 0); id_cmp = rb_intern("<=>"); inspect_key = rb_intern("__inspect_key__"); } /********************************************************************** bignum.c - $Author: wyhaines $ $Date: 2009-07-14 17:05:27 +0200 (Tue, 14 Jul 2009) $ created at: Fri Jun 10 00:48:55 JST 1994 Copyright (C) 1993-2003 Yukihiro Matsumoto **********************************************************************/ #include "ruby.h" #include "rubysig.h" #include #include #ifdef HAVE_IEEEFP_H #include #endif VALUE rb_cBignum; #if defined __MINGW32__ #define USHORT _USHORT #endif #define BDIGITS(x) ((BDIGIT*)RBIGNUM(x)->digits) #define BITSPERDIG (SIZEOF_BDIGITS*CHAR_BIT) #define BIGRAD ((BDIGIT_DBL)1 << BITSPERDIG) #define DIGSPERLONG ((unsigned int)(SIZEOF_LONG/SIZEOF_BDIGITS)) #if HAVE_LONG_LONG # define DIGSPERLL ((unsigned int)(SIZEOF_LONG_LONG/SIZEOF_BDIGITS)) #endif #define BIGUP(x) ((BDIGIT_DBL)(x) << BITSPERDIG) #define BIGDN(x) RSHIFT(x,BITSPERDIG) #define BIGLO(x) ((BDIGIT)((x) & (BIGRAD-1))) #define BDIGMAX ((BDIGIT)-1) #define BIGZEROP(x) (RBIGNUM(x)->len == 0 || \ (BDIGITS(x)[0] == 0 && \ (RBIGNUM(x)->len == 1 || bigzero_p(x)))) static int bigzero_p(VALUE); static int bigzero_p(x) VALUE x; { long i; for (i = 0; i < RBIGNUM(x)->len; ++i) { if (BDIGITS(x)[i]) return 0; } return 1; } static VALUE bignew_1(klass, len, sign) VALUE klass; long len; int sign; { NEWOBJ(big, struct RBignum); OBJSETUP(big, klass, T_BIGNUM); big->sign = sign?1:0; big->len = len; big->digits = ALLOC_N(BDIGIT, len); return (VALUE)big; } #define bignew(len,sign) bignew_1(rb_cBignum,len,sign) VALUE rb_big_clone(x) VALUE x; { VALUE z = bignew_1(CLASS_OF(x), RBIGNUM(x)->len, RBIGNUM(x)->sign); MEMCPY(BDIGITS(z), BDIGITS(x), BDIGIT, RBIGNUM(x)->len); return z; } /* modify a bignum by 2's complement */ static void get2comp(x) VALUE x; { long i = RBIGNUM(x)->len; BDIGIT *ds = BDIGITS(x); BDIGIT_DBL num; if (!i) return; while (i--) ds[i] = ~ds[i]; i = 0; num = 1; do { num += ds[i]; ds[i++] = BIGLO(num); num = BIGDN(num); } while (i < RBIGNUM(x)->len); if (num != 0) { REALLOC_N(RBIGNUM(x)->digits, BDIGIT, ++RBIGNUM(x)->len); ds = BDIGITS(x); ds[RBIGNUM(x)->len-1] = RBIGNUM(x)->sign ? ~0 : 1; } } void rb_big_2comp(x) /* get 2's complement */ VALUE x; { get2comp(x); } static VALUE bigtrunc(x) VALUE x; { long len = RBIGNUM(x)->len; BDIGIT *ds = BDIGITS(x); if (len == 0) return x; while (--len && !ds[len]); RBIGNUM(x)->len = ++len; return x; } static VALUE bigfixize(VALUE x) { long len = RBIGNUM(x)->len; BDIGIT *ds = BDIGITS(x); if (len*SIZEOF_BDIGITS <= sizeof(VALUE)) { long num = 0; while (len--) { num = BIGUP(num) + ds[len]; } if (num >= 0) { if (RBIGNUM(x)->sign) { if (POSFIXABLE(num)) return LONG2FIX(num); } else { if (NEGFIXABLE(-(long)num)) return LONG2FIX(-(long)num); } } } return x; } static VALUE bignorm(VALUE x) { if (!FIXNUM_P(x) && TYPE(x) == T_BIGNUM) { x = bigfixize(bigtrunc(x)); } return x; } VALUE rb_big_norm(x) VALUE x; { return bignorm(x); } VALUE rb_uint2big(n) unsigned long n; { BDIGIT_DBL num = n; long i = 0; BDIGIT *digits; VALUE big; big = bignew(DIGSPERLONG, 1); digits = BDIGITS(big); while (i < DIGSPERLONG) { digits[i++] = BIGLO(num); num = BIGDN(num); } i = DIGSPERLONG; while (--i && !digits[i]) ; RBIGNUM(big)->len = i+1; return big; } VALUE rb_int2big(n) long n; { long neg = 0; VALUE big; if (n < 0) { n = -n; neg = 1; } big = rb_uint2big(n); if (neg) { RBIGNUM(big)->sign = 0; } return big; } VALUE rb_uint2inum(n) unsigned long n; { if (POSFIXABLE(n)) return LONG2FIX(n); return rb_uint2big(n); } VALUE rb_int2inum(n) long n; { if (FIXABLE(n)) return LONG2FIX(n); return rb_int2big(n); } #ifdef HAVE_LONG_LONG void rb_quad_pack(buf, val) char *buf; VALUE val; { LONG_LONG q; val = rb_to_int(val); if (FIXNUM_P(val)) { q = FIX2LONG(val); } else { long len = RBIGNUM(val)->len; BDIGIT *ds; if (len > SIZEOF_LONG_LONG/SIZEOF_BDIGITS) rb_raise(rb_eRangeError, "bignum too big to convert into `quad int'"); ds = BDIGITS(val); q = 0; while (len--) { q = BIGUP(q); q += ds[len]; } if (!RBIGNUM(val)->sign) q = -q; } memcpy(buf, (char*)&q, SIZEOF_LONG_LONG); } VALUE rb_quad_unpack(buf, sign) const char *buf; int sign; { unsigned LONG_LONG q; long neg = 0; long i; BDIGIT *digits; VALUE big; memcpy(&q, buf, SIZEOF_LONG_LONG); if (sign) { if (FIXABLE((LONG_LONG)q)) return LONG2FIX((LONG_LONG)q); if ((LONG_LONG)q < 0) { q = -(LONG_LONG)q; neg = 1; } } else { if (POSFIXABLE(q)) return LONG2FIX(q); } i = 0; big = bignew(DIGSPERLL, 1); digits = BDIGITS(big); while (i < DIGSPERLL) { digits[i++] = BIGLO(q); q = BIGDN(q); } i = DIGSPERLL; while (i-- && !digits[i]) ; RBIGNUM(big)->len = i+1; if (neg) { RBIGNUM(big)->sign = 0; } return bignorm(big); } #else #define QUAD_SIZE 8 void rb_quad_pack(buf, val) char *buf; VALUE val; { long len; memset(buf, 0, QUAD_SIZE); val = rb_to_int(val); if (FIXNUM_P(val)) { val = rb_int2big(FIX2LONG(val)); } len = RBIGNUM(val)->len * SIZEOF_BDIGITS; if (len > QUAD_SIZE) { rb_raise(rb_eRangeError, "bignum too big to convert into `quad int'"); } memcpy(buf, (char*)BDIGITS(val), len); if (!RBIGNUM(val)->sign) { len = QUAD_SIZE; while (len--) { *buf = ~*buf; buf++; } } } #define BNEG(b) (RSHIFT(((BDIGIT*)b)[QUAD_SIZE/SIZEOF_BDIGITS-1],BITSPERDIG-1) != 0) VALUE rb_quad_unpack(buf, sign) const char *buf; int sign; { VALUE big = bignew(QUAD_SIZE/SIZEOF_BDIGITS, 1); memcpy((char*)BDIGITS(big), buf, QUAD_SIZE); if (sign && BNEG(buf)) { long len = QUAD_SIZE; char *tmp = (char*)BDIGITS(big); RBIGNUM(big)->sign = 0; while (len--) { *tmp = ~*tmp; tmp++; } } return bignorm(big); } #endif VALUE rb_cstr_to_inum(str, base, badcheck) const char *str; int base; int badcheck; { const char *s = str; char *end; char sign = 1, nondigit = 0; int c; BDIGIT_DBL num; long len, blen = 1; long i; VALUE z; BDIGIT *zds; #define conv_digit(c) \ (!ISASCII(c) ? -1 : \ isdigit(c) ? ((c) - '0') : \ islower(c) ? ((c) - 'a' + 10) : \ isupper(c) ? ((c) - 'A' + 10) : \ -1) if (!str) { if (badcheck) goto bad; return INT2FIX(0); } if (badcheck) { while (ISSPACE(*str)) str++; } else { while (ISSPACE(*str) || *str == '_') str++; } if (str[0] == '+') { str++; } else if (str[0] == '-') { str++; sign = 0; } if (str[0] == '+' || str[0] == '-') { if (badcheck) goto bad; return INT2FIX(0); } if (base <= 0) { if (str[0] == '0') { switch (str[1]) { case 'x': case 'X': base = 16; break; case 'b': case 'B': base = 2; break; case 'o': case 'O': base = 8; break; case 'd': case 'D': base = 10; break; default: base = 8; } } else if (base < -1) { base = -base; } else { base = 10; } } switch (base) { case 2: len = 1; if (str[0] == '0' && (str[1] == 'b'||str[1] == 'B')) { str += 2; } break; case 3: len = 2; break; case 8: if (str[0] == '0' && (str[1] == 'o'||str[1] == 'O')) { str += 2; } case 4: case 5: case 6: case 7: len = 3; break; case 10: if (str[0] == '0' && (str[1] == 'd'||str[1] == 'D')) { str += 2; } case 9: case 11: case 12: case 13: case 14: case 15: len = 4; break; case 16: len = 4; if (str[0] == '0' && (str[1] == 'x'||str[1] == 'X')) { str += 2; } break; default: if (base < 2 || 36 < base) { rb_raise(rb_eArgError, "illegal radix %d", base); } if (base <= 32) { len = 5; } else { len = 6; } break; } if (*str == '0') { /* squeeze preceeding 0s */ while (*++str == '0'); if (!(c = *str) || ISSPACE(c)) --str; } c = *str; c = conv_digit(c); if (c < 0 || c >= base) { if (badcheck) goto bad; return INT2FIX(0); } len *= strlen(str)*sizeof(char); if (len <= (sizeof(VALUE)*CHAR_BIT)) { unsigned long val = strtoul((char*)str, &end, base); if (*end == '_') goto bigparse; if (badcheck) { if (end == str) goto bad; /* no number */ while (*end && ISSPACE(*end)) end++; if (*end) goto bad; /* trailing garbage */ } if (POSFIXABLE(val)) { if (sign) return LONG2FIX(val); else { long result = -(long)val; return LONG2FIX(result); } } else { VALUE big = rb_uint2big(val); RBIGNUM(big)->sign = sign; return bignorm(big); } } bigparse: len = (len/BITSPERDIG)+1; if (badcheck && *str == '_') goto bad; z = bignew(len, sign); zds = BDIGITS(z); for (i=len;i--;) zds[i]=0; while ((c = *str++) != 0) { if (c == '_') { if (badcheck) { if (nondigit) goto bad; nondigit = c; } continue; } else if ((c = conv_digit(c)) < 0) { break; } if (c >= base) break; nondigit = 0; i = 0; num = c; for (;;) { while (iptr; } if (s) { len = RSTRING(str)->len; if (s[len]) { /* no sentinel somehow */ char *p = ALLOCA_N(char, len+1); MEMCPY(p, s, char, len); p[len] = '\0'; s = p; } } return rb_cstr_to_inum(s, base, badcheck); } #if HAVE_LONG_LONG VALUE rb_ull2big(n) unsigned LONG_LONG n; { BDIGIT_DBL num = n; long i = 0; BDIGIT *digits; VALUE big; big = bignew(DIGSPERLL, 1); digits = BDIGITS(big); while (i < DIGSPERLL) { digits[i++] = BIGLO(num); num = BIGDN(num); } i = DIGSPERLL; while (i-- && !digits[i]) ; RBIGNUM(big)->len = i+1; return big; } VALUE rb_ll2big(n) LONG_LONG n; { long neg = 0; VALUE big; if (n < 0) { n = -n; neg = 1; } big = rb_ull2big(n); if (neg) { RBIGNUM(big)->sign = 0; } return big; } VALUE rb_ull2inum(n) unsigned LONG_LONG n; { if (POSFIXABLE(n)) return LONG2FIX(n); return rb_ull2big(n); } VALUE rb_ll2inum(n) LONG_LONG n; { if (FIXABLE(n)) return LONG2FIX(n); return rb_ll2big(n); } #endif /* HAVE_LONG_LONG */ VALUE rb_cstr2inum(str, base) const char *str; int base; { return rb_cstr_to_inum(str, base, base==0); } VALUE rb_str2inum(str, base) VALUE str; int base; { return rb_str_to_inum(str, base, base==0); } const char ruby_digitmap[] = "0123456789abcdefghijklmnopqrstuvwxyz"; VALUE rb_big2str0(x, base, trim) VALUE x; int base; int trim; { volatile VALUE t; BDIGIT *ds; long i, j, hbase; VALUE ss; char *s; if (FIXNUM_P(x)) { return rb_fix2str(x, base); } i = RBIGNUM(x)->len; if (BIGZEROP(x)) { return rb_str_new2("0"); } if (i >= LONG_MAX/SIZEOF_BDIGITS/CHAR_BIT) { rb_raise(rb_eRangeError, "bignum too big to convert into `string'"); } j = SIZEOF_BDIGITS*CHAR_BIT*i; switch (base) { case 2: break; case 3: j = j * 53L / 84 + 1; break; case 4: case 5: case 6: case 7: j = (j + 1) / 2; break; case 8: case 9: j = (j + 2) / 3; break; case 10: case 11: case 12: case 13: case 14: case 15: j = j * 28L / 93 + 1; break; case 16: case 17: case 18: case 19: case 20: case 21: case 22: case 23: case 24: case 25: case 26: case 27: case 28: case 29: case 30: case 31: j = (j + 3) / 4; break; case 32: case 33: case 34: case 35: case 36: j = (j + 4) / 5; break; default: rb_raise(rb_eArgError, "illegal radix %d", base); break; } j++; /* space for sign */ hbase = base * base; #if SIZEOF_BDIGITS > 2 hbase *= hbase; #endif t = rb_big_clone(x); ds = BDIGITS(t); ss = rb_str_new(0, j+1); s = RSTRING(ss)->ptr; s[0] = RBIGNUM(x)->sign ? '+' : '-'; TRAP_BEG; while (i && j > 1) { long k = i; BDIGIT_DBL num = 0; while (k--) { num = BIGUP(num) + ds[k]; ds[k] = (BDIGIT)(num / hbase); num %= hbase; } if (trim && ds[i-1] == 0) i--; k = SIZEOF_BDIGITS; while (k--) { s[--j] = ruby_digitmap[num % base]; num /= base; if (!trim && j <= 1) break; if (trim && i == 0 && num == 0) break; } } if (trim) {while (s[j] == '0') j++;} i = RSTRING(ss)->len - j; if (RBIGNUM(x)->sign) { memmove(s, s+j, i); RSTRING(ss)->len = i-1; } else { memmove(s+1, s+j, i); RSTRING(ss)->len = i; } s[RSTRING(ss)->len] = '\0'; TRAP_END; return ss; } VALUE rb_big2str(VALUE x, int base) { return rb_big2str0(x, base, Qtrue); } /* * call-seq: * big.to_s(base=10) => string * * Returns a string containing the representation of big radix * base (2 through 36). * * 12345654321.to_s #=> "12345654321" * 12345654321.to_s(2) #=> "1011011111110110111011110000110001" * 12345654321.to_s(8) #=> "133766736061" * 12345654321.to_s(16) #=> "2dfdbbc31" * 78546939656932.to_s(36) #=> "rubyrules" */ static VALUE rb_big_to_s(argc, argv, x) int argc; VALUE *argv; VALUE x; { VALUE b; int base; rb_scan_args(argc, argv, "01", &b); if (argc == 0) base = 10; else base = NUM2INT(b); return rb_big2str(x, base); } static unsigned long big2ulong(x, type) VALUE x; char *type; { long len = RBIGNUM(x)->len; BDIGIT_DBL num; BDIGIT *ds; if (len > SIZEOF_LONG/SIZEOF_BDIGITS) rb_raise(rb_eRangeError, "bignum too big to convert into `%s'", type); ds = BDIGITS(x); num = 0; while (len--) { num = BIGUP(num); num += ds[len]; } return num; } unsigned long rb_big2ulong_pack(x) VALUE x; { unsigned long num = big2ulong(x, "unsigned long"); if (!RBIGNUM(x)->sign) { return -num; } return num; } unsigned long rb_big2ulong(x) VALUE x; { unsigned long num = big2ulong(x, "unsigned long"); if (!RBIGNUM(x)->sign) { if ((long)num < 0) { rb_raise(rb_eRangeError, "bignum out of range of unsigned long"); } return -num; } return num; } long rb_big2long(x) VALUE x; { unsigned long num = big2ulong(x, "long"); if ((long)num < 0 && (RBIGNUM(x)->sign || (long)num != LONG_MIN)) { rb_raise(rb_eRangeError, "bignum too big to convert into `long'"); } if (!RBIGNUM(x)->sign) return -(long)num; return num; } #if HAVE_LONG_LONG static unsigned LONG_LONG big2ull(x, type) VALUE x; char *type; { long len = RBIGNUM(x)->len; BDIGIT_DBL num; BDIGIT *ds; if (len > SIZEOF_LONG_LONG/SIZEOF_BDIGITS) rb_raise(rb_eRangeError, "bignum too big to convert into `%s'", type); ds = BDIGITS(x); num = 0; while (len--) { num = BIGUP(num); num += ds[len]; } return num; } unsigned LONG_LONG rb_big2ull(x) VALUE x; { unsigned LONG_LONG num = big2ull(x, "unsigned long long"); if (!RBIGNUM(x)->sign) return -num; return num; } LONG_LONG rb_big2ll(x) VALUE x; { unsigned LONG_LONG num = big2ull(x, "long long"); if ((LONG_LONG)num < 0 && (RBIGNUM(x)->sign || (LONG_LONG)num != LLONG_MIN)) { rb_raise(rb_eRangeError, "bignum too big to convert into `long long'"); } if (!RBIGNUM(x)->sign) return -(LONG_LONG)num; return num; } #endif /* HAVE_LONG_LONG */ static VALUE dbl2big(d) double d; { long i = 0; BDIGIT c; BDIGIT *digits; VALUE z; double u = (d < 0)?-d:d; if (isinf(d)) { rb_raise(rb_eFloatDomainError, d < 0 ? "-Infinity" : "Infinity"); } if (isnan(d)) { rb_raise(rb_eFloatDomainError, "NaN"); } while (!POSFIXABLE(u) || 0 != (long)u) { u /= (double)(BIGRAD); i++; } z = bignew(i, d>=0); digits = BDIGITS(z); while (i--) { u *= BIGRAD; c = (BDIGIT)u; u -= c; digits[i] = c; } return z; } VALUE rb_dbl2big(d) double d; { return bignorm(dbl2big(d)); } double rb_big2dbl(x) VALUE x; { double d = 0.0; long i = RBIGNUM(x)->len; BDIGIT *ds = BDIGITS(x); while (i--) { d = ds[i] + BIGRAD*d; } if (isinf(d)) { rb_warn("Bignum out of Float range"); d = HUGE_VAL; } if (!RBIGNUM(x)->sign) d = -d; return d; } /* * call-seq: * big.to_f -> float * * Converts big to a Float. If big doesn't * fit in a Float, the result is infinity. * */ static VALUE rb_big_to_f(x) VALUE x; { return rb_float_new(rb_big2dbl(x)); } /* * call-seq: * big <=> numeric => -1, 0, +1 * * Comparison---Returns -1, 0, or +1 depending on whether big is * less than, equal to, or greater than numeric. This is the * basis for the tests in Comparable. * */ static VALUE rb_big_cmp(x, y) VALUE x, y; { long xlen = RBIGNUM(x)->len; switch (TYPE(y)) { case T_FIXNUM: y = rb_int2big(FIX2LONG(y)); break; case T_BIGNUM: break; case T_FLOAT: { double a = RFLOAT(y)->value; if (isinf(a)) { if (a > 0.0) return INT2FIX(-1); else return INT2FIX(1); } return rb_dbl_cmp(rb_big2dbl(x), a); } default: return rb_num_coerce_cmp(x, y); } if (RBIGNUM(x)->sign > RBIGNUM(y)->sign) return INT2FIX(1); if (RBIGNUM(x)->sign < RBIGNUM(y)->sign) return INT2FIX(-1); if (xlen < RBIGNUM(y)->len) return (RBIGNUM(x)->sign) ? INT2FIX(-1) : INT2FIX(1); if (xlen > RBIGNUM(y)->len) return (RBIGNUM(x)->sign) ? INT2FIX(1) : INT2FIX(-1); while(xlen-- && (BDIGITS(x)[xlen]==BDIGITS(y)[xlen])); if (-1 == xlen) return INT2FIX(0); return (BDIGITS(x)[xlen] > BDIGITS(y)[xlen]) ? (RBIGNUM(x)->sign ? INT2FIX(1) : INT2FIX(-1)) : (RBIGNUM(x)->sign ? INT2FIX(-1) : INT2FIX(1)); } /* * call-seq: * big == obj => true or false * * Returns true only if obj has the same value * as big. Contrast this with Bignum#eql?, which * requires obj to be a Bignum. * * 68719476736 == 68719476736.0 #=> true */ static VALUE rb_big_eq(x, y) VALUE x, y; { switch (TYPE(y)) { case T_FIXNUM: y = rb_int2big(FIX2LONG(y)); break; case T_BIGNUM: break; case T_FLOAT: { volatile double a, b; a = RFLOAT(y)->value; if (isnan(a)) return Qfalse; b = rb_big2dbl(x); return (a == b)?Qtrue:Qfalse; } default: return rb_equal(y, x); } if (RBIGNUM(x)->sign != RBIGNUM(y)->sign) return Qfalse; if (RBIGNUM(x)->len != RBIGNUM(y)->len) return Qfalse; if (MEMCMP(BDIGITS(x),BDIGITS(y),BDIGIT,RBIGNUM(y)->len) != 0) return Qfalse; return Qtrue; } /* * call-seq: * big.eql?(obj) => true or false * * Returns true only if obj is a * Bignum with the same value as big. Contrast this * with Bignum#==, which performs type conversions. * * 68719476736.eql?(68719476736.0) #=> false */ static VALUE rb_big_eql(x, y) VALUE x, y; { if (TYPE(y) != T_BIGNUM) return Qfalse; if (RBIGNUM(x)->sign != RBIGNUM(y)->sign) return Qfalse; if (RBIGNUM(x)->len != RBIGNUM(y)->len) return Qfalse; if (MEMCMP(BDIGITS(x),BDIGITS(y),BDIGIT,RBIGNUM(y)->len) != 0) return Qfalse; return Qtrue; } /* * call-seq: * -big => other_big * * Unary minus (returns a new Bignum whose value is 0-big) */ static VALUE rb_big_uminus(x) VALUE x; { VALUE z = rb_big_clone(x); RBIGNUM(z)->sign = !RBIGNUM(x)->sign; return bignorm(z); } /* * call-seq: * ~big => integer * * Inverts the bits in big. As Bignums are conceptually infinite * length, the result acts as if it had an infinite number of one * bits to the left. In hex representations, this is displayed * as two periods to the left of the digits. * * sprintf("%X", ~0x1122334455) #=> "..FEEDDCCBBAA" */ static VALUE rb_big_neg(x) VALUE x; { VALUE z = rb_big_clone(x); long i; BDIGIT *ds; if (!RBIGNUM(x)->sign) get2comp(z); ds = BDIGITS(z); i = RBIGNUM(x)->len; if (!i) return INT2FIX(~0); while (i--) ds[i] = ~ds[i]; RBIGNUM(z)->sign = !RBIGNUM(z)->sign; if (RBIGNUM(x)->sign) get2comp(z); return bignorm(z); } static VALUE bigsub(x, y) VALUE x, y; { VALUE z = 0; BDIGIT *zds; BDIGIT_DBL_SIGNED num; long i = RBIGNUM(x)->len; /* if x is larger than y, swap */ if (RBIGNUM(x)->len < RBIGNUM(y)->len) { z = x; x = y; y = z; /* swap x y */ } else if (RBIGNUM(x)->len == RBIGNUM(y)->len) { while (i > 0) { i--; if (BDIGITS(x)[i] > BDIGITS(y)[i]) { break; } if (BDIGITS(x)[i] < BDIGITS(y)[i]) { z = x; x = y; y = z; /* swap x y */ break; } } } z = bignew(RBIGNUM(x)->len, z==0); zds = BDIGITS(z); for (i = 0, num = 0; i < RBIGNUM(y)->len; i++) { num += (BDIGIT_DBL_SIGNED)BDIGITS(x)[i] - BDIGITS(y)[i]; zds[i] = BIGLO(num); num = BIGDN(num); } while (num && i < RBIGNUM(x)->len) { num += BDIGITS(x)[i]; zds[i++] = BIGLO(num); num = BIGDN(num); } while (i < RBIGNUM(x)->len) { zds[i] = BDIGITS(x)[i]; i++; } return z; } static VALUE bigadd(x, y, sign) VALUE x, y; int sign; { VALUE z; BDIGIT_DBL num; long i, len; sign = (sign == RBIGNUM(y)->sign); if (RBIGNUM(x)->sign != sign) { if (sign) return bigsub(y, x); return bigsub(x, y); } if (RBIGNUM(x)->len > RBIGNUM(y)->len) { len = RBIGNUM(x)->len + 1; z = x; x = y; y = z; } else { len = RBIGNUM(y)->len + 1; } z = bignew(len, sign); len = RBIGNUM(x)->len; for (i = 0, num = 0; i < len; i++) { num += (BDIGIT_DBL)BDIGITS(x)[i] + BDIGITS(y)[i]; BDIGITS(z)[i] = BIGLO(num); num = BIGDN(num); } len = RBIGNUM(y)->len; while (num && i < len) { num += BDIGITS(y)[i]; BDIGITS(z)[i++] = BIGLO(num); num = BIGDN(num); } while (i < len) { BDIGITS(z)[i] = BDIGITS(y)[i]; i++; } BDIGITS(z)[i] = (BDIGIT)num; return z; } /* * call-seq: * big + other => Numeric * * Adds big and other, returning the result. */ VALUE rb_big_plus(x, y) VALUE x, y; { switch (TYPE(y)) { case T_FIXNUM: y = rb_int2big(FIX2LONG(y)); /* fall through */ case T_BIGNUM: return bignorm(bigadd(x, y, 1)); case T_FLOAT: return rb_float_new(rb_big2dbl(x) + RFLOAT(y)->value); default: return rb_num_coerce_bin(x, y); } } /* * call-seq: * big - other => Numeric * * Subtracts other from big, returning the result. */ VALUE rb_big_minus(x, y) VALUE x, y; { switch (TYPE(y)) { case T_FIXNUM: y = rb_int2big(FIX2LONG(y)); /* fall through */ case T_BIGNUM: return bignorm(bigadd(x, y, 0)); case T_FLOAT: return rb_float_new(rb_big2dbl(x) - RFLOAT(y)->value); default: return rb_num_coerce_bin(x, y); } } VALUE rb_big_mul0(x, y) VALUE x, y; { long i, j; BDIGIT_DBL n = 0; VALUE z; BDIGIT *zds; if (FIXNUM_P(x)) x = rb_int2big(FIX2LONG(x)); switch (TYPE(y)) { case T_FIXNUM: y = rb_int2big(FIX2LONG(y)); break; case T_BIGNUM: break; case T_FLOAT: return rb_float_new(rb_big2dbl(x) * RFLOAT(y)->value); default: return rb_num_coerce_bin(x, y); } j = RBIGNUM(x)->len + RBIGNUM(y)->len + 1; z = bignew(j, RBIGNUM(x)->sign==RBIGNUM(y)->sign); zds = BDIGITS(z); while (j--) zds[j] = 0; for (i = 0; i < RBIGNUM(x)->len; i++) { BDIGIT_DBL dd = BDIGITS(x)[i]; if (dd == 0) continue; n = 0; for (j = 0; j < RBIGNUM(y)->len; j++) { BDIGIT_DBL ee = n + (BDIGIT_DBL)dd * BDIGITS(y)[j]; n = zds[i + j] + ee; if (ee) zds[i + j] = BIGLO(n); n = BIGDN(n); } if (n) { zds[i + j] = n; } } return z; } /* * call-seq: * big * other => Numeric * * Multiplies big and other, returning the result. */ VALUE rb_big_mul(x, y) VALUE x, y; { return bignorm(rb_big_mul0(x, y)); } static void bigdivrem(x, y, divp, modp) VALUE x, y; VALUE *divp, *modp; { long nx = RBIGNUM(x)->len, ny = RBIGNUM(y)->len; long i, j; VALUE yy, z; BDIGIT *xds, *yds, *zds, *tds; BDIGIT_DBL t2; BDIGIT_DBL_SIGNED num; BDIGIT dd, q; if (BIGZEROP(y)) rb_num_zerodiv(); yds = BDIGITS(y); if (nx < ny || (nx == ny && BDIGITS(x)[nx - 1] < BDIGITS(y)[ny - 1])) { if (divp) *divp = rb_int2big(0); if (modp) *modp = x; return; } xds = BDIGITS(x); if (ny == 1) { dd = yds[0]; z = rb_big_clone(x); zds = BDIGITS(z); t2 = 0; i = nx; while (i--) { t2 = BIGUP(t2) + zds[i]; zds[i] = (BDIGIT)(t2 / dd); t2 %= dd; } RBIGNUM(z)->sign = RBIGNUM(x)->sign==RBIGNUM(y)->sign; if (modp) { *modp = rb_uint2big((unsigned long)t2); RBIGNUM(*modp)->sign = RBIGNUM(x)->sign; } if (divp) *divp = z; return; } z = bignew(nx==ny?nx+2:nx+1, RBIGNUM(x)->sign==RBIGNUM(y)->sign); zds = BDIGITS(z); if (nx==ny) zds[nx+1] = 0; while (!yds[ny-1]) ny--; dd = 0; q = yds[ny-1]; while ((q & (1<<(BITSPERDIG-1))) == 0) { q <<= 1; dd++; } if (dd) { yy = rb_big_clone(y); tds = BDIGITS(yy); j = 0; t2 = 0; while (j= ny); if (divp) { /* move quotient down in z */ *divp = rb_big_clone(z); zds = BDIGITS(*divp); j = (nx==ny ? nx+2 : nx+1) - ny; for (i = 0;i < j;i++) zds[i] = zds[i+ny]; RBIGNUM(*divp)->len = i; } if (modp) { /* normalize remainder */ *modp = rb_big_clone(z); zds = BDIGITS(*modp); while (--ny && !zds[ny]); ++ny; if (dd) { t2 = 0; i = ny; while(i--) { t2 = (t2 | zds[i]) >> dd; q = zds[i]; zds[i] = BIGLO(t2); t2 = BIGUP(q); } } RBIGNUM(*modp)->len = ny; RBIGNUM(*modp)->sign = RBIGNUM(x)->sign; } } static void bigdivmod(x, y, divp, modp) VALUE x, y; VALUE *divp, *modp; { VALUE mod; bigdivrem(x, y, divp, &mod); if (RBIGNUM(x)->sign != RBIGNUM(y)->sign && !BIGZEROP(mod)) { if (divp) *divp = bigadd(*divp, rb_int2big(1), 0); if (modp) *modp = bigadd(mod, y, 1); } else { if (divp) *divp = *divp; if (modp) *modp = mod; } } /* * call-seq: * big / other => Numeric * big.div(other) => Numeric * * Divides big by other, returning the result. */ static VALUE rb_big_div(x, y) VALUE x, y; { VALUE z; switch (TYPE(y)) { case T_FIXNUM: y = rb_int2big(FIX2LONG(y)); break; case T_BIGNUM: break; case T_FLOAT: return rb_float_new(rb_big2dbl(x) / RFLOAT(y)->value); default: return rb_num_coerce_bin(x, y); } bigdivmod(x, y, &z, 0); return bignorm(z); } /* * call-seq: * big % other => Numeric * big.modulo(other) => Numeric * * Returns big modulo other. See Numeric.divmod for more * information. */ static VALUE rb_big_modulo(x, y) VALUE x, y; { VALUE z; switch (TYPE(y)) { case T_FIXNUM: y = rb_int2big(FIX2LONG(y)); break; case T_BIGNUM: break; default: return rb_num_coerce_bin(x, y); } bigdivmod(x, y, 0, &z); return bignorm(z); } /* * call-seq: * big.remainder(numeric) => number * * Returns the remainder after dividing big by numeric. * * -1234567890987654321.remainder(13731) #=> -6966 * -1234567890987654321.remainder(13731.24) #=> -9906.22531493148 */ static VALUE rb_big_remainder(x, y) VALUE x, y; { VALUE z; switch (TYPE(y)) { case T_FIXNUM: y = rb_int2big(FIX2LONG(y)); break; case T_BIGNUM: break; default: return rb_num_coerce_bin(x, y); } bigdivrem(x, y, 0, &z); return bignorm(z); } static VALUE big_lshift _((VALUE, unsigned long)); static VALUE big_rshift _((VALUE, unsigned long)); static VALUE big_shift(x, n) VALUE x; int n; { if (n < 0) return big_lshift(x, (unsigned int)n); else if (n > 0) return big_rshift(x, (unsigned int)n); return x; } /* * call-seq: * big.divmod(numeric) => array * * See Numeric#divmod. * */ VALUE rb_big_divmod(x, y) VALUE x, y; { VALUE div, mod; switch (TYPE(y)) { case T_FIXNUM: y = rb_int2big(FIX2LONG(y)); break; case T_BIGNUM: break; default: return rb_num_coerce_bin(x, y); } bigdivmod(x, y, &div, &mod); return rb_assoc_new(bignorm(div), bignorm(mod)); } /* * call-seq: * big.quo(numeric) -> float * * Returns the floating point result of dividing big by * numeric. * * -1234567890987654321.quo(13731) #=> -89910996357705.5 * -1234567890987654321.quo(13731.24) #=> -89909424858035.7 * */ static VALUE rb_big_quo(x, y) VALUE x, y; { double dx = rb_big2dbl(x); double dy; switch (TYPE(y)) { case T_FIXNUM: dy = (double)FIX2LONG(y); break; case T_BIGNUM: dy = rb_big2dbl(y); break; case T_FLOAT: dy = RFLOAT(y)->value; break; default: return rb_num_coerce_bin(x, y); } return rb_float_new(dx / dy); } /* * call-seq: * big ** exponent #=> numeric * * Raises _big_ to the _exponent_ power (which may be an integer, float, * or anything that will coerce to a number). The result may be * a Fixnum, Bignum, or Float * * 123456789 ** 2 #=> 15241578750190521 * 123456789 ** 1.2 #=> 5126464716.09932 * 123456789 ** -2 #=> 6.5610001194102e-17 */ VALUE rb_big_pow(x, y) VALUE x, y; { double d; long yy; if (y == INT2FIX(0)) return INT2FIX(1); switch (TYPE(y)) { case T_FLOAT: d = RFLOAT(y)->value; break; case T_BIGNUM: rb_warn("in a**b, b may be too big"); d = rb_big2dbl(y); break; case T_FIXNUM: yy = FIX2LONG(y); if (yy > 0) { VALUE z = x; const long BIGLEN_LIMIT = 1024*1024 / SIZEOF_BDIGITS; if ((RBIGNUM(x)->len > BIGLEN_LIMIT) || (RBIGNUM(x)->len > BIGLEN_LIMIT / yy)) { rb_warn("in a**b, b may be too big"); d = (double)yy; break; } for (;;) { yy -= 1; if (yy == 0) break; while (yy % 2 == 0) { yy /= 2; x = rb_big_mul0(x, x); bigtrunc(x); } z = rb_big_mul0(z, x); bigtrunc(z); } return bignorm(z); } d = (double)yy; break; default: return rb_num_coerce_bin(x, y); } return rb_float_new(pow(rb_big2dbl(x), d)); } /* * call-seq: * big & numeric => integer * * Performs bitwise +and+ between _big_ and _numeric_. */ VALUE rb_big_and(xx, yy) VALUE xx, yy; { volatile VALUE x, y, z; BDIGIT *ds1, *ds2, *zds; long i, l1, l2; char sign; x = xx; y = rb_to_int(yy); if (FIXNUM_P(y)) { y = rb_int2big(FIX2LONG(y)); } if (!RBIGNUM(y)->sign) { y = rb_big_clone(y); get2comp(y); } if (!RBIGNUM(x)->sign) { x = rb_big_clone(x); get2comp(x); } if (RBIGNUM(x)->len > RBIGNUM(y)->len) { l1 = RBIGNUM(y)->len; l2 = RBIGNUM(x)->len; ds1 = BDIGITS(y); ds2 = BDIGITS(x); sign = RBIGNUM(y)->sign; } else { l1 = RBIGNUM(x)->len; l2 = RBIGNUM(y)->len; ds1 = BDIGITS(x); ds2 = BDIGITS(y); sign = RBIGNUM(x)->sign; } z = bignew(l2, RBIGNUM(x)->sign || RBIGNUM(y)->sign); zds = BDIGITS(z); for (i=0; isign) get2comp(z); return bignorm(z); } /* * call-seq: * big | numeric => integer * * Performs bitwise +or+ between _big_ and _numeric_. */ VALUE rb_big_or(xx, yy) VALUE xx, yy; { volatile VALUE x, y, z; BDIGIT *ds1, *ds2, *zds; long i, l1, l2; char sign; x = xx; y = rb_to_int(yy); if (FIXNUM_P(y)) { y = rb_int2big(FIX2LONG(y)); } if (!RBIGNUM(y)->sign) { y = rb_big_clone(y); get2comp(y); } if (!RBIGNUM(x)->sign) { x = rb_big_clone(x); get2comp(x); } if (RBIGNUM(x)->len > RBIGNUM(y)->len) { l1 = RBIGNUM(y)->len; l2 = RBIGNUM(x)->len; ds1 = BDIGITS(y); ds2 = BDIGITS(x); sign = RBIGNUM(y)->sign; } else { l1 = RBIGNUM(x)->len; l2 = RBIGNUM(y)->len; ds1 = BDIGITS(x); ds2 = BDIGITS(y); sign = RBIGNUM(x)->sign; } z = bignew(l2, RBIGNUM(x)->sign && RBIGNUM(y)->sign); zds = BDIGITS(z); for (i=0; isign) get2comp(z); return bignorm(z); } /* * call-seq: * big ^ numeric => integer * * Performs bitwise +exclusive or+ between _big_ and _numeric_. */ VALUE rb_big_xor(xx, yy) VALUE xx, yy; { volatile VALUE x, y; VALUE z; BDIGIT *ds1, *ds2, *zds; long i, l1, l2; char sign; x = xx; y = rb_to_int(yy); if (FIXNUM_P(y)) { y = rb_int2big(FIX2LONG(y)); } if (!RBIGNUM(y)->sign) { y = rb_big_clone(y); get2comp(y); } if (!RBIGNUM(x)->sign) { x = rb_big_clone(x); get2comp(x); } if (RBIGNUM(x)->len > RBIGNUM(y)->len) { l1 = RBIGNUM(y)->len; l2 = RBIGNUM(x)->len; ds1 = BDIGITS(y); ds2 = BDIGITS(x); sign = RBIGNUM(y)->sign; } else { l1 = RBIGNUM(x)->len; l2 = RBIGNUM(y)->len; ds1 = BDIGITS(x); ds2 = BDIGITS(y); sign = RBIGNUM(x)->sign; } RBIGNUM(x)->sign = RBIGNUM(x)->sign?1:0; RBIGNUM(y)->sign = RBIGNUM(y)->sign?1:0; z = bignew(l2, !(RBIGNUM(x)->sign ^ RBIGNUM(y)->sign)); zds = BDIGITS(z); for (i=0; isign) get2comp(z); return bignorm(z); } static VALUE check_shiftdown(VALUE y, VALUE x) { if (!RBIGNUM(x)->len) return INT2FIX(0); if (RBIGNUM(y)->len > SIZEOF_LONG / SIZEOF_BDIGITS) { return RBIGNUM(x)->sign ? INT2FIX(0) : INT2FIX(-1); } return Qnil; } /* * call-seq: * big << numeric => integer * * Shifts big left _numeric_ positions (right if _numeric_ is negative). */ VALUE rb_big_lshift(x, y) VALUE x, y; { long shift; int neg = 0; for (;;) { if (FIXNUM_P(y)) { shift = FIX2LONG(y); if (shift < 0) { neg = 1; shift = -shift; } break; } else if (TYPE(y) == T_BIGNUM) { if (!RBIGNUM(y)->sign) { VALUE t = check_shiftdown(y, x); if (!NIL_P(t)) return t; neg = 1; } shift = big2ulong(y, "long", Qtrue); break; } y = rb_to_int(y); } if (neg) return big_rshift(x, shift); return big_lshift(x, shift); } static VALUE big_lshift(x, shift) VALUE x; unsigned long shift; { BDIGIT *xds, *zds; long s1 = shift/BITSPERDIG; int s2 = shift%BITSPERDIG; VALUE z; BDIGIT_DBL num = 0; long len, i; len = RBIGNUM(x)->len; z = bignew(len+s1+1, RBIGNUM(x)->sign); zds = BDIGITS(z); for (i=0; i> numeric => integer * * Shifts big right _numeric_ positions (left if _numeric_ is negative). */ VALUE rb_big_rshift(x, y) VALUE x, y; { long shift; int neg = 0; for (;;) { if (FIXNUM_P(y)) { shift = FIX2LONG(y); if (shift < 0) { neg = 1; shift = -shift; } break; } else if (TYPE(y) == T_BIGNUM) { if (RBIGNUM(y)->sign) { VALUE t = check_shiftdown(y, x); if (!NIL_P(t)) return t; } else { neg = 1; } shift = big2ulong(y, "long", Qtrue); break; } y = rb_to_int(y); } if (neg) return big_lshift(x, shift); return big_rshift(x, shift); } static VALUE big_rshift(x, shift) VALUE x; unsigned long shift; { BDIGIT *xds, *zds; long s1 = shift/BITSPERDIG; int s2 = shift%BITSPERDIG; VALUE z; BDIGIT_DBL num = 0; long i, j; volatile VALUE save_x; if (s1 > RBIGNUM(x)->len) { if (RBIGNUM(x)->sign) return INT2FIX(0); else return INT2FIX(-1); } if (!RBIGNUM(x)->sign) { save_x = x = rb_big_clone(x); get2comp(x); } xds = BDIGITS(x); i = RBIGNUM(x)->len; j = i - s1; if (j == 0) { if (RBIGNUM(x)->sign) return INT2FIX(0); else return INT2FIX(-1); } z = bignew(j, RBIGNUM(x)->sign); if (!RBIGNUM(x)->sign) { num = ((BDIGIT_DBL)~0) << BITSPERDIG; } zds = BDIGITS(z); while (i--, j--) { num = (num | xds[i]) >> s2; zds[j] = BIGLO(num); num = BIGUP(xds[i]); } if (!RBIGNUM(x)->sign) { get2comp(z); } return bignorm(z); } /* * call-seq: * big[n] -> 0, 1 * * Bit Reference---Returns the nth bit in the (assumed) binary * representation of big, where big[0] is the least * significant bit. * * a = 9**15 * 50.downto(0) do |n| * print a[n] * end * * produces: * * 000101110110100000111000011110010100111100010111001 * */ static VALUE rb_big_aref(x, y) VALUE x, y; { BDIGIT *xds; BDIGIT_DBL num; unsigned long shift; long i, s1, s2; if (TYPE(y) == T_BIGNUM) { if (!RBIGNUM(y)->sign) return INT2FIX(0); if (RBIGNUM(bigtrunc(y))->len > SIZEOF_LONG/SIZEOF_BDIGITS) { out_of_range: return RBIGNUM(x)->sign ? INT2FIX(0) : INT2FIX(1); } shift = big2ulong(y, "long", Qfalse); } else { i = NUM2LONG(y); if (i < 0) return INT2FIX(0); shift = (VALUE)i; } s1 = shift/BITSPERDIG; s2 = shift%BITSPERDIG; if (s1 >= RBIGNUM(x)->len) goto out_of_range; if (!RBIGNUM(x)->sign) { xds = BDIGITS(x); i = 0; num = 1; while (num += ~xds[i], ++i <= s1) { num = BIGDN(num); } } else { num = BDIGITS(x)[s1]; } if (num & ((BDIGIT_DBL)1< fixnum * * Compute a hash based on the value of _big_. */ static VALUE rb_big_hash(x) VALUE x; { long i, len, key; BDIGIT *digits; key = 0; digits = BDIGITS(x); len = RBIGNUM(x)->len; for (i=0; i aBignum * * Returns the absolute value of big. * * -1234567890987654321.abs #=> 1234567890987654321 */ static VALUE rb_big_abs(x) VALUE x; { if (!RBIGNUM(x)->sign) { x = rb_big_clone(x); RBIGNUM(x)->sign = 1; } return x; } VALUE rb_big_rand(max, rand_buf) VALUE max; double *rand_buf; { VALUE v; long len = RBIGNUM(max)->len; if (BIGZEROP(max)) { return rb_float_new(rand_buf[0]); } v = bignew(len,1); len--; BDIGITS(v)[len] = BDIGITS(max)[len] * rand_buf[len]; while (len--) { BDIGITS(v)[len] = ((BDIGIT)~0) * rand_buf[len]; } return v; } /* * call-seq: * big.size -> integer * * Returns the number of bytes in the machine representation of * big. * * (256**10 - 1).size #=> 12 * (256**20 - 1).size #=> 20 * (256**40 - 1).size #=> 40 */ static VALUE rb_big_size(big) VALUE big; { return LONG2FIX(RBIGNUM(big)->len*SIZEOF_BDIGITS); } /* * Bignum objects hold integers outside the range of * Fixnum. Bignum objects are created * automatically when integer calculations would otherwise overflow a * Fixnum. When a calculation involving * Bignum objects returns a result that will fit in a * Fixnum, the result is automatically converted. * * For the purposes of the bitwise operations and [], a * Bignum is treated as if it were an infinite-length * bitstring with 2's complement representation. * * While Fixnum values are immediate, Bignum * objects are not---assignment and parameter passing work with * references to objects, not the objects themselves. * */ void Init_Bignum() { rb_cBignum = rb_define_class("Bignum", rb_cInteger); rb_define_method(rb_cBignum, "to_s", rb_big_to_s, -1); rb_define_method(rb_cBignum, "coerce", rb_big_coerce, 1); rb_define_method(rb_cBignum, "-@", rb_big_uminus, 0); rb_define_method(rb_cBignum, "+", rb_big_plus, 1); rb_define_method(rb_cBignum, "-", rb_big_minus, 1); rb_define_method(rb_cBignum, "*", rb_big_mul, 1); rb_define_method(rb_cBignum, "/", rb_big_div, 1); rb_define_method(rb_cBignum, "%", rb_big_modulo, 1); rb_define_method(rb_cBignum, "div", rb_big_div, 1); rb_define_method(rb_cBignum, "divmod", rb_big_divmod, 1); rb_define_method(rb_cBignum, "modulo", rb_big_modulo, 1); rb_define_method(rb_cBignum, "remainder", rb_big_remainder, 1); rb_define_method(rb_cBignum, "quo", rb_big_quo, 1); rb_define_method(rb_cBignum, "**", rb_big_pow, 1); rb_define_method(rb_cBignum, "&", rb_big_and, 1); rb_define_method(rb_cBignum, "|", rb_big_or, 1); rb_define_method(rb_cBignum, "^", rb_big_xor, 1); rb_define_method(rb_cBignum, "~", rb_big_neg, 0); rb_define_method(rb_cBignum, "<<", rb_big_lshift, 1); rb_define_method(rb_cBignum, ">>", rb_big_rshift, 1); rb_define_method(rb_cBignum, "[]", rb_big_aref, 1); rb_define_method(rb_cBignum, "<=>", rb_big_cmp, 1); rb_define_method(rb_cBignum, "==", rb_big_eq, 1); rb_define_method(rb_cBignum, "eql?", rb_big_eql, 1); rb_define_method(rb_cBignum, "hash", rb_big_hash, 0); rb_define_method(rb_cBignum, "to_f", rb_big_to_f, 0); rb_define_method(rb_cBignum, "abs", rb_big_abs, 0); rb_define_method(rb_cBignum, "size", rb_big_size, 0); } /********************************************************************** class.c - $Author: shyouhei $ $Date: 2009-01-16 02:58:45 +0100 (Fri, 16 Jan 2009) $ created at: Tue Aug 10 15:05:44 JST 1993 Copyright (C) 1993-2003 Yukihiro Matsumoto **********************************************************************/ #include "ruby.h" #include "rubysig.h" #include "node.h" #include "st.h" #include extern st_table *rb_class_tbl; VALUE rb_class_boot(super) VALUE super; { NEWOBJ(klass, struct RClass); OBJSETUP(klass, rb_cClass, T_CLASS); klass->super = super; klass->iv_tbl = 0; klass->m_tbl = 0; /* safe GC */ klass->m_tbl = st_init_numtable(); OBJ_INFECT(klass, super); return (VALUE)klass; } VALUE rb_class_new(super) VALUE super; { Check_Type(super, T_CLASS); if (super == rb_cClass) { rb_raise(rb_eTypeError, "can't make subclass of Class"); } if (FL_TEST(super, FL_SINGLETON)) { rb_raise(rb_eTypeError, "can't make subclass of virtual class"); } return rb_class_boot(super); } struct clone_method_data { st_table *tbl; VALUE klass; }; static int clone_method(mid, body, data) ID mid; NODE *body; struct clone_method_data *data; { NODE *fbody = body->nd_body; if (fbody && nd_type(fbody) == NODE_SCOPE) { NODE *cref = (NODE*)fbody->nd_rval; if (cref) cref = cref->nd_next; fbody = rb_copy_node_scope(fbody, NEW_CREF(data->klass, cref)); } st_insert(data->tbl, mid, (st_data_t)NEW_METHOD(fbody, body->nd_noex)); return ST_CONTINUE; } /* :nodoc: */ VALUE rb_mod_init_copy(clone, orig) VALUE clone, orig; { rb_obj_init_copy(clone, orig); if (!FL_TEST(CLASS_OF(clone), FL_SINGLETON)) { RBASIC(clone)->klass = RBASIC(orig)->klass; RBASIC(clone)->klass = rb_singleton_class_clone(clone); } RCLASS(clone)->super = RCLASS(orig)->super; if (RCLASS(orig)->iv_tbl) { ID id; RCLASS(clone)->iv_tbl = st_copy(RCLASS(orig)->iv_tbl); id = rb_intern("__classpath__"); st_delete(RCLASS(clone)->iv_tbl, (st_data_t*)&id, 0); id = rb_intern("__classid__"); st_delete(RCLASS(clone)->iv_tbl, (st_data_t*)&id, 0); } if (RCLASS(orig)->m_tbl) { struct clone_method_data data; data.tbl = RCLASS(clone)->m_tbl = st_init_numtable(); data.klass = (VALUE)clone; st_foreach(RCLASS(orig)->m_tbl, clone_method, (st_data_t)&data); } return clone; } /* :nodoc: */ VALUE rb_class_init_copy(clone, orig) VALUE clone, orig; { if (RCLASS(clone)->super != 0) { rb_raise(rb_eTypeError, "already initialized class"); } if (FL_TEST(orig, FL_SINGLETON)) { rb_raise(rb_eTypeError, "can't copy singleton class"); } return rb_mod_init_copy(clone, orig); } VALUE rb_singleton_class_clone(obj) VALUE obj; { VALUE klass = RBASIC(obj)->klass; if (!FL_TEST(klass, FL_SINGLETON)) return klass; else { /* copy singleton(unnamed) class */ NEWOBJ(clone, struct RClass); OBJSETUP(clone, 0, RBASIC(klass)->flags); if (BUILTIN_TYPE(obj) == T_CLASS) { RBASIC(clone)->klass = (VALUE)clone; } else { RBASIC(clone)->klass = rb_singleton_class_clone(klass); } clone->super = RCLASS(klass)->super; clone->iv_tbl = 0; clone->m_tbl = 0; if (RCLASS(klass)->iv_tbl) { clone->iv_tbl = st_copy(RCLASS(klass)->iv_tbl); } { struct clone_method_data data; data.tbl = clone->m_tbl = st_init_numtable(); switch (TYPE(obj)) { case T_CLASS: case T_MODULE: data.klass = obj; break; default: data.klass = 0; break; } st_foreach(RCLASS(klass)->m_tbl, clone_method, (st_data_t)&data); } rb_singleton_class_attached(RBASIC(clone)->klass, (VALUE)clone); FL_SET(clone, FL_SINGLETON); return (VALUE)clone; } } void rb_singleton_class_attached(klass, obj) VALUE klass, obj; { if (FL_TEST(klass, FL_SINGLETON)) { if (!RCLASS(klass)->iv_tbl) { RCLASS(klass)->iv_tbl = st_init_numtable(); } st_insert(RCLASS(klass)->iv_tbl, rb_intern("__attached__"), obj); } } VALUE rb_make_metaclass(obj, super) VALUE obj, super; { VALUE klass = rb_class_boot(super); FL_SET(klass, FL_SINGLETON); RBASIC(obj)->klass = klass; rb_singleton_class_attached(klass, obj); if (BUILTIN_TYPE(obj) == T_CLASS && FL_TEST(obj, FL_SINGLETON)) { RBASIC(klass)->klass = klass; RCLASS(klass)->super = RBASIC(rb_class_real(RCLASS(obj)->super))->klass; } else { VALUE metasuper = RBASIC(rb_class_real(super))->klass; /* metaclass of a superclass may be NULL at boot time */ if (metasuper) { RBASIC(klass)->klass = metasuper; } } return klass; } VALUE rb_define_class_id(id, super) ID id; VALUE super; { VALUE klass; if (!super) super = rb_cObject; klass = rb_class_new(super); rb_make_metaclass(klass, RBASIC(super)->klass); return klass; } void rb_check_inheritable(super) VALUE super; { if (TYPE(super) != T_CLASS) { rb_raise(rb_eTypeError, "superclass must be a Class (%s given)", rb_obj_classname(super)); } if (RBASIC(super)->flags & FL_SINGLETON) { rb_raise(rb_eTypeError, "can't make subclass of virtual class"); } } VALUE rb_class_inherited(super, klass) VALUE super, klass; { if (!super) super = rb_cObject; return rb_funcall(super, rb_intern("inherited"), 1, klass); } VALUE rb_define_class(name, super) const char *name; VALUE super; { VALUE klass; ID id; id = rb_intern(name); if (rb_const_defined(rb_cObject, id)) { klass = rb_const_get(rb_cObject, id); if (TYPE(klass) != T_CLASS) { rb_raise(rb_eTypeError, "%s is not a class", name); } if (rb_class_real(RCLASS(klass)->super) != super) { rb_name_error(id, "%s is already defined", name); } return klass; } if (!super) { rb_warn("no super class for `%s', Object assumed", name); } klass = rb_define_class_id(id, super); st_add_direct(rb_class_tbl, id, klass); rb_name_class(klass, id); rb_const_set(rb_cObject, id, klass); rb_class_inherited(super, klass); return klass; } VALUE rb_define_class_under(outer, name, super) VALUE outer; const char *name; VALUE super; { VALUE klass; ID id; id = rb_intern(name); if (rb_const_defined_at(outer, id)) { klass = rb_const_get_at(outer, id); if (TYPE(klass) != T_CLASS) { rb_raise(rb_eTypeError, "%s is not a class", name); } if (rb_class_real(RCLASS(klass)->super) != super) { rb_name_error(id, "%s is already defined", name); } return klass; } if (!super) { rb_warn("no super class for `%s::%s', Object assumed", rb_class2name(outer), name); } klass = rb_define_class_id(id, super); rb_set_class_path(klass, outer, name); rb_const_set(outer, id, klass); rb_class_inherited(super, klass); return klass; } VALUE rb_module_new() { NEWOBJ(mdl, struct RClass); OBJSETUP(mdl, rb_cModule, T_MODULE); mdl->super = 0; mdl->iv_tbl = 0; mdl->m_tbl = 0; mdl->m_tbl = st_init_numtable(); return (VALUE)mdl; } VALUE rb_define_module_id(id) ID id; { VALUE mdl; mdl = rb_module_new(); rb_name_class(mdl, id); return mdl; } VALUE rb_define_module(name) const char *name; { VALUE module; ID id; id = rb_intern(name); if (rb_const_defined(rb_cObject, id)) { module = rb_const_get(rb_cObject, id); if (TYPE(module) == T_MODULE) return module; rb_raise(rb_eTypeError, "%s is not a module", rb_obj_classname(module)); } module = rb_define_module_id(id); st_add_direct(rb_class_tbl, id, module); rb_const_set(rb_cObject, id, module); return module; } VALUE rb_define_module_under(outer, name) VALUE outer; const char *name; { VALUE module; ID id; id = rb_intern(name); if (rb_const_defined_at(outer, id)) { module = rb_const_get_at(outer, id); if (TYPE(module) == T_MODULE) return module; rb_raise(rb_eTypeError, "%s::%s is not a module", rb_class2name(outer), rb_obj_classname(module)); } module = rb_define_module_id(id); rb_const_set(outer, id, module); rb_set_class_path(module, outer, name); return module; } static VALUE include_class_new(module, super) VALUE module, super; { NEWOBJ(klass, struct RClass); OBJSETUP(klass, rb_cClass, T_ICLASS); if (BUILTIN_TYPE(module) == T_ICLASS) { module = RBASIC(module)->klass; } if (!RCLASS(module)->iv_tbl) { RCLASS(module)->iv_tbl = st_init_numtable(); } klass->iv_tbl = RCLASS(module)->iv_tbl; klass->m_tbl = RCLASS(module)->m_tbl; klass->super = super; if (TYPE(module) == T_ICLASS) { RBASIC(klass)->klass = RBASIC(module)->klass; } else { RBASIC(klass)->klass = module; } OBJ_INFECT(klass, module); OBJ_INFECT(klass, super); return (VALUE)klass; } void rb_include_module(klass, module) VALUE klass, module; { VALUE p, c; int changed = 0; rb_frozen_class_p(klass); if (!OBJ_TAINTED(klass)) { rb_secure(4); } if (TYPE(module) != T_MODULE) { Check_Type(module, T_MODULE); } OBJ_INFECT(klass, module); c = klass; while (module) { int superclass_seen = Qfalse; if (RCLASS(klass)->m_tbl == RCLASS(module)->m_tbl) rb_raise(rb_eArgError, "cyclic include detected"); /* ignore if the module included already in superclasses */ for (p = RCLASS(klass)->super; p; p = RCLASS(p)->super) { switch (BUILTIN_TYPE(p)) { case T_ICLASS: if (RCLASS(p)->m_tbl == RCLASS(module)->m_tbl) { if (!superclass_seen) { c = p; /* move insertion point */ } goto skip; } break; case T_CLASS: superclass_seen = Qtrue; break; } } c = RCLASS(c)->super = include_class_new(module, RCLASS(c)->super); changed = 1; skip: module = RCLASS(module)->super; } if (changed) rb_clear_cache(); } /* * call-seq: * mod.included_modules -> array * * Returns the list of modules included in mod. * * module Mixin * end * * module Outer * include Mixin * end * * Mixin.included_modules #=> [] * Outer.included_modules #=> [Mixin] */ VALUE rb_mod_included_modules(mod) VALUE mod; { VALUE ary = rb_ary_new(); VALUE p; for (p = RCLASS(mod)->super; p; p = RCLASS(p)->super) { if (BUILTIN_TYPE(p) == T_ICLASS) { rb_ary_push(ary, RBASIC(p)->klass); } } return ary; } /* * call-seq: * mod.include?(module) => true or false * * Returns true if module is included in * mod or one of mod's ancestors. * * module A * end * class B * include A * end * class C < B * end * B.include?(A) #=> true * C.include?(A) #=> true * A.include?(A) #=> false */ VALUE rb_mod_include_p(mod, mod2) VALUE mod; VALUE mod2; { VALUE p; Check_Type(mod2, T_MODULE); for (p = RCLASS(mod)->super; p; p = RCLASS(p)->super) { if (BUILTIN_TYPE(p) == T_ICLASS) { if (RBASIC(p)->klass == mod2) return Qtrue; } } return Qfalse; } /* * call-seq: * mod.ancestors -> array * * Returns a list of modules included in mod (including * mod itself). * * module Mod * include Math * include Comparable * end * * Mod.ancestors #=> [Mod, Comparable, Math] * Math.ancestors #=> [Math] */ VALUE rb_mod_ancestors(mod) VALUE mod; { VALUE p, ary = rb_ary_new(); for (p = mod; p; p = RCLASS(p)->super) { if (FL_TEST(p, FL_SINGLETON)) continue; if (BUILTIN_TYPE(p) == T_ICLASS) { rb_ary_push(ary, RBASIC(p)->klass); } else { rb_ary_push(ary, p); } } return ary; } #define VISI(x) ((x)&NOEX_MASK) #define VISI_CHECK(x,f) (VISI(x) == (f)) static int ins_methods_push(name, type, ary, visi) ID name; long type; VALUE ary; long visi; { if (type == -1) return ST_CONTINUE; switch (visi) { case NOEX_PRIVATE: case NOEX_PROTECTED: case NOEX_PUBLIC: visi = (type == visi); break; default: visi = (type != NOEX_PRIVATE); break; } if (visi) { rb_ary_push(ary, rb_str_new2(rb_id2name(name))); } return ST_CONTINUE; } static int ins_methods_i(name, type, ary) ID name; long type; VALUE ary; { return ins_methods_push(name, type, ary, -1); /* everything but private */ } static int ins_methods_prot_i(name, type, ary) ID name; long type; VALUE ary; { return ins_methods_push(name, type, ary, NOEX_PROTECTED); } static int ins_methods_priv_i(name, type, ary) ID name; long type; VALUE ary; { return ins_methods_push(name, type, ary, NOEX_PRIVATE); } static int ins_methods_pub_i(name, type, ary) ID name; long type; VALUE ary; { return ins_methods_push(name, type, ary, NOEX_PUBLIC); } static int method_entry(key, body, list) ID key; NODE *body; st_table *list; { long type; if (key == ID_ALLOCATOR) return ST_CONTINUE; if (!st_lookup(list, key, 0)) { if (!body->nd_body) type = -1; /* none */ else type = VISI(body->nd_noex); st_add_direct(list, key, type); } return ST_CONTINUE; } static VALUE class_instance_method_list(argc, argv, mod, func) int argc; VALUE *argv; VALUE mod; int (*func) _((ID, long, VALUE)); { VALUE ary; int recur; st_table *list; if (argc == 0) { recur = Qtrue; } else { VALUE r; rb_scan_args(argc, argv, "01", &r); recur = RTEST(r); } list = st_init_numtable(); for (; mod; mod = RCLASS(mod)->super) { st_foreach(RCLASS(mod)->m_tbl, method_entry, (st_data_t)list); if (BUILTIN_TYPE(mod) == T_ICLASS) continue; if (FL_TEST(mod, FL_SINGLETON)) continue; if (!recur) break; } ary = rb_ary_new(); st_foreach(list, func, ary); st_free_table(list); return ary; } /* * call-seq: * mod.instance_methods(include_super=true) => array * * Returns an array containing the names of public instance methods in * the receiver. For a module, these are the public methods; for a * class, they are the instance (not singleton) methods. With no * argument, or with an argument that is false, the * instance methods in mod are returned, otherwise the methods * in mod and mod's superclasses are returned. * * module A * def method1() end * end * class B * def method2() end * end * class C < B * def method3() end * end * * A.instance_methods #=> ["method1"] * B.instance_methods(false) #=> ["method2"] * C.instance_methods(false) #=> ["method3"] * C.instance_methods(true).length #=> 43 */ VALUE rb_class_instance_methods(argc, argv, mod) int argc; VALUE *argv; VALUE mod; { return class_instance_method_list(argc, argv, mod, ins_methods_i); } /* * call-seq: * mod.protected_instance_methods(include_super=true) => array * * Returns a list of the protected instance methods defined in * mod. If the optional parameter is not false, the * methods of any ancestors are included. */ VALUE rb_class_protected_instance_methods(argc, argv, mod) int argc; VALUE *argv; VALUE mod; { return class_instance_method_list(argc, argv, mod, ins_methods_prot_i); } /* * call-seq: * mod.private_instance_methods(include_super=true) => array * * Returns a list of the private instance methods defined in * mod. If the optional parameter is not false, the * methods of any ancestors are included. * * module Mod * def method1() end * private :method1 * def method2() end * end * Mod.instance_methods #=> ["method2"] * Mod.private_instance_methods #=> ["method1"] */ VALUE rb_class_private_instance_methods(argc, argv, mod) int argc; VALUE *argv; VALUE mod; { return class_instance_method_list(argc, argv, mod, ins_methods_priv_i); } /* * call-seq: * mod.public_instance_methods(include_super=true) => array * * Returns a list of the public instance methods defined in mod. * If the optional parameter is not false, the methods of * any ancestors are included. */ VALUE rb_class_public_instance_methods(argc, argv, mod) int argc; VALUE *argv; VALUE mod; { return class_instance_method_list(argc, argv, mod, ins_methods_pub_i); } /* * call-seq: * obj.singleton_methods(all=true) => array * * Returns an array of the names of singleton methods for obj. * If the optional all parameter is true, the list will include * methods in modules included in obj. * * module Other * def three() end * end * * class Single * def Single.four() end * end * * a = Single.new * * def a.one() * end * * class << a * include Other * def two() * end * end * * Single.singleton_methods #=> ["four"] * a.singleton_methods(false) #=> ["two", "one"] * a.singleton_methods #=> ["two", "one", "three"] */ VALUE rb_obj_singleton_methods(argc, argv, obj) int argc; VALUE *argv; VALUE obj; { VALUE recur, ary, klass; st_table *list; rb_scan_args(argc, argv, "01", &recur); if (argc == 0) { recur = Qtrue; } klass = CLASS_OF(obj); list = st_init_numtable(); if (klass && FL_TEST(klass, FL_SINGLETON)) { st_foreach(RCLASS(klass)->m_tbl, method_entry, (st_data_t)list); klass = RCLASS(klass)->super; } if (RTEST(recur)) { while (klass && (FL_TEST(klass, FL_SINGLETON) || TYPE(klass) == T_ICLASS)) { st_foreach(RCLASS(klass)->m_tbl, method_entry, (st_data_t)list); klass = RCLASS(klass)->super; } } ary = rb_ary_new(); st_foreach(list, ins_methods_i, ary); st_free_table(list); return ary; } void rb_define_method_id(klass, name, func, argc) VALUE klass; ID name; VALUE (*func)(); int argc; { rb_add_method(klass, name, NEW_CFUNC(func,argc), NOEX_PUBLIC); } void rb_define_method(klass, name, func, argc) VALUE klass; const char *name; VALUE (*func)(); int argc; { ID id = rb_intern(name); int ex = NOEX_PUBLIC; rb_add_method(klass, id, NEW_CFUNC(func, argc), ex); } void rb_define_protected_method(klass, name, func, argc) VALUE klass; const char *name; VALUE (*func)(); int argc; { rb_add_method(klass, rb_intern(name), NEW_CFUNC(func, argc), NOEX_PROTECTED); } void rb_define_private_method(klass, name, func, argc) VALUE klass; const char *name; VALUE (*func)(); int argc; { rb_add_method(klass, rb_intern(name), NEW_CFUNC(func, argc), NOEX_PRIVATE); } void rb_undef_method(klass, name) VALUE klass; const char *name; { rb_add_method(klass, rb_intern(name), 0, NOEX_UNDEF); } #define SPECIAL_SINGLETON(x,c) do {\ if (obj == (x)) {\ return c;\ }\ } while (0) VALUE rb_singleton_class(obj) VALUE obj; { VALUE klass; if (FIXNUM_P(obj) || SYMBOL_P(obj)) { rb_raise(rb_eTypeError, "can't define singleton"); } if (rb_special_const_p(obj)) { SPECIAL_SINGLETON(Qnil, rb_cNilClass); SPECIAL_SINGLETON(Qfalse, rb_cFalseClass); SPECIAL_SINGLETON(Qtrue, rb_cTrueClass); rb_bug("unknown immediate %ld", obj); } DEFER_INTS; if (FL_TEST(RBASIC(obj)->klass, FL_SINGLETON) && rb_iv_get(RBASIC(obj)->klass, "__attached__") == obj) { klass = RBASIC(obj)->klass; } else { klass = rb_make_metaclass(obj, RBASIC(obj)->klass); } if (OBJ_TAINTED(obj)) { OBJ_TAINT(klass); } else { FL_UNSET(klass, FL_TAINT); } if (OBJ_FROZEN(obj)) OBJ_FREEZE(klass); ALLOW_INTS; return klass; } void rb_define_singleton_method(obj, name, func, argc) VALUE obj; const char *name; VALUE (*func)(); int argc; { rb_define_method(rb_singleton_class(obj), name, func, argc); } void rb_define_module_function(module, name, func, argc) VALUE module; const char *name; VALUE (*func)(); int argc; { rb_define_private_method(module, name, func, argc); rb_define_singleton_method(module, name, func, argc); } void rb_define_global_function(name, func, argc) const char *name; VALUE (*func)(); int argc; { rb_define_module_function(rb_mKernel, name, func, argc); } void rb_define_alias(klass, name1, name2) VALUE klass; const char *name1, *name2; { rb_alias(klass, rb_intern(name1), rb_intern(name2)); } void rb_define_attr(klass, name, read, write) VALUE klass; const char *name; int read, write; { rb_attr(klass, rb_intern(name), read, write, Qfalse); } #ifdef HAVE_STDARG_PROTOTYPES #include #define va_init_list(a,b) va_start(a,b) #else #include #define va_init_list(a,b) va_start(a) #endif int #ifdef HAVE_STDARG_PROTOTYPES rb_scan_args(int argc, const VALUE *argv, const char *fmt, ...) #else rb_scan_args(argc, argv, fmt, va_alist) int argc; const VALUE *argv; const char *fmt; va_dcl #endif { int n, i = 0; const char *p = fmt; VALUE *var; va_list vargs; va_init_list(vargs, fmt); if (*p == '*') goto rest_arg; if (ISDIGIT(*p)) { n = *p - '0'; if (n > argc) rb_raise(rb_eArgError, "wrong number of arguments (%d for %d)", argc, n); for (i=0; i i) { if (var) *var = argv[i]; } else { if (var) *var = Qnil; } } p++; } if(*p == '*') { rest_arg: var = va_arg(vargs, VALUE*); if (argc > i) { if (var) *var = rb_ary_new4(argc-i, argv+i); i = argc; } else { if (var) *var = rb_ary_new(); } p++; } if (*p == '&') { var = va_arg(vargs, VALUE*); if (rb_block_given_p()) { *var = rb_block_proc(); } else { *var = Qnil; } p++; } va_end(vargs); if (*p != '\0') { goto error; } if (argc > i) { rb_raise(rb_eArgError, "wrong number of arguments (%d for %d)", argc, i); } return argc; error: rb_fatal("bad scan arg format: %s", fmt); return 0; } /********************************************************************** compar.c - $Author: shyouhei $ $Date: 2007-02-13 00:01:19 +0100 (Tue, 13 Feb 2007) $ created at: Thu Aug 26 14:39:48 JST 1993 Copyright (C) 1993-2003 Yukihiro Matsumoto **********************************************************************/ #include "ruby.h" VALUE rb_mComparable; static ID cmp; int rb_cmpint(val, a, b) VALUE val, a, b; { if (NIL_P(val)) { rb_cmperr(a, b); } if (FIXNUM_P(val)) return FIX2INT(val); if (TYPE(val) == T_BIGNUM) { if (RBIGNUM(val)->sign) return 1; return -1; } if (RTEST(rb_funcall(val, '>', 1, INT2FIX(0)))) return 1; if (RTEST(rb_funcall(val, '<', 1, INT2FIX(0)))) return -1; return 0; } void rb_cmperr(x, y) VALUE x, y; { const char *classname; if (SPECIAL_CONST_P(y)) { y = rb_inspect(y); classname = StringValuePtr(y); } else { classname = rb_obj_classname(y); } rb_raise(rb_eArgError, "comparison of %s with %s failed", rb_obj_classname(x), classname); } #define cmperr() (rb_cmperr(x, y), Qnil) static VALUE cmp_eq(a) VALUE *a; { VALUE c = rb_funcall(a[0], cmp, 1, a[1]); if (NIL_P(c)) return Qnil; if (rb_cmpint(c, a[0], a[1]) == 0) return Qtrue; return Qfalse; } static VALUE cmp_failed() { return Qnil; } /* * call-seq: * obj == other => true or false * * Compares two objects based on the receiver's <=> * method, returning true if it returns 0. Also returns true if * _obj_ and _other_ are the same object. */ static VALUE cmp_equal(x, y) VALUE x, y; { VALUE a[2]; if (x == y) return Qtrue; a[0] = x; a[1] = y; return rb_rescue(cmp_eq, (VALUE)a, cmp_failed, 0); } /* * call-seq: * obj > other => true or false * * Compares two objects based on the receiver's <=> * method, returning true if it returns 1. */ static VALUE cmp_gt(x, y) VALUE x, y; { VALUE c = rb_funcall(x, cmp, 1, y); if (NIL_P(c)) return cmperr(); if (rb_cmpint(c, x, y) > 0) return Qtrue; return Qfalse; } /* * call-seq: * obj >= other => true or false * * Compares two objects based on the receiver's <=> * method, returning true if it returns 0 or 1. */ static VALUE cmp_ge(x, y) VALUE x, y; { VALUE c = rb_funcall(x, cmp, 1, y); if (NIL_P(c)) return cmperr(); if (rb_cmpint(c, x, y) >= 0) return Qtrue; return Qfalse; } /* * call-seq: * obj < other => true or false * * Compares two objects based on the receiver's <=> * method, returning true if it returns -1. */ static VALUE cmp_lt(x, y) VALUE x, y; { VALUE c = rb_funcall(x, cmp, 1, y); if (NIL_P(c)) return cmperr(); if (rb_cmpint(c, x, y) < 0) return Qtrue; return Qfalse; } /* * call-seq: * obj <= other => true or false * * Compares two objects based on the receiver's <=> * method, returning true if it returns -1 or 0. */ static VALUE cmp_le(x, y) VALUE x, y; { VALUE c = rb_funcall(x, cmp, 1, y); if (NIL_P(c)) return cmperr(); if (rb_cmpint(c, x, y) <= 0) return Qtrue; return Qfalse; } /* * call-seq: * obj.between?(min, max) => true or false * * Returns false if obj <=> * min is less than zero or if anObject <=> * max is greater than zero, true otherwise. * * 3.between?(1, 5) #=> true * 6.between?(1, 5) #=> false * 'cat'.between?('ant', 'dog') #=> true * 'gnu'.between?('ant', 'dog') #=> false * */ static VALUE cmp_between(x, min, max) VALUE x, min, max; { if (RTEST(cmp_lt(x, min))) return Qfalse; if (RTEST(cmp_gt(x, max))) return Qfalse; return Qtrue; } /* * The Comparable mixin is used by classes whose objects * may be ordered. The class must define the <=> operator, * which compares the receiver against another object, returning -1, 0, * or +1 depending on whether the receiver is less than, equal to, or * greater than the other object. Comparable uses * <=> to implement the conventional comparison operators * (<, <=, ==, >=, * and >) and the method between?. * * class SizeMatters * include Comparable * attr :str * def <=>(anOther) * str.size <=> anOther.str.size * end * def initialize(str) * @str = str * end * def inspect * @str * end * end * * s1 = SizeMatters.new("Z") * s2 = SizeMatters.new("YY") * s3 = SizeMatters.new("XXX") * s4 = SizeMatters.new("WWWW") * s5 = SizeMatters.new("VVVVV") * * s1 < s2 #=> true * s4.between?(s1, s3) #=> false * s4.between?(s3, s5) #=> true * [ s3, s2, s5, s4, s1 ].sort #=> [Z, YY, XXX, WWWW, VVVVV] * */ void Init_Comparable() { rb_mComparable = rb_define_module("Comparable"); rb_define_method(rb_mComparable, "==", cmp_equal, 1); rb_define_method(rb_mComparable, ">", cmp_gt, 1); rb_define_method(rb_mComparable, ">=", cmp_ge, 1); rb_define_method(rb_mComparable, "<", cmp_lt, 1); rb_define_method(rb_mComparable, "<=", cmp_le, 1); rb_define_method(rb_mComparable, "between?", cmp_between, 2); cmp = rb_intern("<=>"); } /********************************************************************** dir.c - $Author: shyouhei $ $Date: 2009-02-04 06:26:31 +0100 (Wed, 04 Feb 2009) $ created at: Wed Jan 5 09:51:01 JST 1994 Copyright (C) 1993-2003 Yukihiro Matsumoto Copyright (C) 2000 Network Applied Communication Laboratory, Inc. Copyright (C) 2000 Information-technology Promotion Agency, Japan **********************************************************************/ #include "ruby.h" #include #include #ifdef HAVE_UNISTD_H #include #endif #if defined HAVE_DIRENT_H && !defined _WIN32 # include # define NAMLEN(dirent) strlen((dirent)->d_name) #elif defined HAVE_DIRECT_H && !defined _WIN32 # include # define NAMLEN(dirent) strlen((dirent)->d_name) #else # define dirent direct # if !defined __NeXT__ # define NAMLEN(dirent) (dirent)->d_namlen # else # /* On some versions of NextStep, d_namlen is always zero, so avoid it. */ # define NAMLEN(dirent) strlen((dirent)->d_name) # endif # if HAVE_SYS_NDIR_H # include # endif # if HAVE_SYS_DIR_H # include # endif # if HAVE_NDIR_H # include # endif # ifdef _WIN32 # include "win32/dir.h" # endif #endif #include #ifndef HAVE_STDLIB_H char *getenv(); #endif #ifndef HAVE_STRING_H char *strchr _((char*,char)); #endif #include #include "util.h" #if !defined HAVE_LSTAT && !defined lstat #define lstat stat #endif #ifndef CASEFOLD_FILESYSTEM # if defined DOSISH || defined __VMS # define CASEFOLD_FILESYSTEM 1 # else # define CASEFOLD_FILESYSTEM 0 # endif #endif #define FNM_NOESCAPE 0x01 #define FNM_PATHNAME 0x02 #define FNM_DOTMATCH 0x04 #define FNM_CASEFOLD 0x08 #if CASEFOLD_FILESYSTEM #define FNM_SYSCASE FNM_CASEFOLD #else #define FNM_SYSCASE 0 #endif #define FNM_NOMATCH 1 #define FNM_ERROR 2 #define downcase(c) (nocase && ISUPPER(c) ? tolower(c) : (c)) #define compare(c1, c2) (((unsigned char)(c1)) - ((unsigned char)(c2))) /* caution: in case *p == '\0' Next(p) == p + 1 in single byte environment Next(p) == p in multi byte environment */ #if defined(CharNext) # define Next(p) CharNext(p) #elif defined(DJGPP) # define Next(p) ((p) + mblen(p, RUBY_MBCHAR_MAXSIZE)) #elif defined(__EMX__) # define Next(p) ((p) + emx_mblen(p)) static inline int emx_mblen(const char *p) { int n = mblen(p, RUBY_MBCHAR_MAXSIZE); return (n < 0) ? 1 : n; } #endif #ifndef Next /* single byte environment */ # define Next(p) ((p) + 1) # define Inc(p) (++(p)) # define Compare(p1, p2) (compare(downcase(*(p1)), downcase(*(p2)))) #else /* multi byte environment */ # define Inc(p) ((p) = Next(p)) # define Compare(p1, p2) (CompareImpl(p1, p2, nocase)) static int CompareImpl(const char *p1, const char *p2, int nocase) { const int len1 = Next(p1) - p1; const int len2 = Next(p2) - p2; #ifdef _WIN32 char buf1[10], buf2[10]; /* large enough? */ #endif if (len1 < 0 || len2 < 0) { rb_fatal("CompareImpl: negative len"); } if (len1 == 0) return len2; if (len2 == 0) return -len1; #ifdef _WIN32 if (nocase && rb_w32_iswinnt()) { if (len1 > 1) { if (len1 >= sizeof(buf1)) { rb_fatal("CompareImpl: too large len"); } memcpy(buf1, p1, len1); buf1[len1] = '\0'; CharLower(buf1); p1 = buf1; /* trick */ } if (len2 > 1) { if (len2 >= sizeof(buf2)) { rb_fatal("CompareImpl: too large len"); } memcpy(buf2, p2, len2); buf2[len2] = '\0'; CharLower(buf2); p2 = buf2; /* trick */ } } #endif if (len1 == 1) if (len2 == 1) return compare(downcase(*p1), downcase(*p2)); else { const int ret = compare(downcase(*p1), *p2); return ret ? ret : -1; } else if (len2 == 1) { const int ret = compare(*p1, downcase(*p2)); return ret ? ret : 1; } else { const int ret = memcmp(p1, p2, len1 < len2 ? len1 : len2); return ret ? ret : len1 - len2; } } #endif /* environment */ static char * bracket(p, s, flags) const char *p; /* pattern (next to '[') */ const char *s; /* string */ int flags; { const int nocase = flags & FNM_CASEFOLD; const int escape = !(flags & FNM_NOESCAPE); int ok = 0, not = 0; if (*p == '!' || *p == '^') { not = 1; p++; } while (*p != ']') { const char *t1 = p; if (escape && *t1 == '\\') t1++; if (!*t1) return NULL; p = Next(t1); if (p[0] == '-' && p[1] != ']') { const char *t2 = p + 1; if (escape && *t2 == '\\') t2++; if (!*t2) return NULL; p = Next(t2); if (!ok && Compare(t1, s) <= 0 && Compare(s, t2) <= 0) ok = 1; } else if (!ok && Compare(t1, s) == 0) ok = 1; } return ok == not ? NULL : (char *)p + 1; } /* If FNM_PATHNAME is set, only path element will be matched. (upto '/' or '\0') Otherwise, entire string will be matched. End marker itself won't be compared. And if function succeeds, *pcur reaches end marker. */ #define UNESCAPE(p) (escape && *(p) == '\\' ? (p) + 1 : (p)) #define ISEND(p) (!*(p) || (pathname && *(p) == '/')) #define RETURN(val) return *pcur = p, *scur = s, (val); static int fnmatch_helper(pcur, scur, flags) const char **pcur; /* pattern */ const char **scur; /* string */ int flags; { const int period = !(flags & FNM_DOTMATCH); const int pathname = flags & FNM_PATHNAME; const int escape = !(flags & FNM_NOESCAPE); const int nocase = flags & FNM_CASEFOLD; const char *ptmp = 0; const char *stmp = 0; const char *p = *pcur; const char *s = *scur; if (period && *s == '.' && *UNESCAPE(p) != '.') /* leading period */ RETURN(FNM_NOMATCH); while (1) { switch (*p) { case '*': do { p++; } while (*p == '*'); if (ISEND(UNESCAPE(p))) { p = UNESCAPE(p); RETURN(0); } if (ISEND(s)) RETURN(FNM_NOMATCH); ptmp = p; stmp = s; continue; case '?': if (ISEND(s)) RETURN(FNM_NOMATCH); p++; Inc(s); continue; case '[': { const char *t; if (ISEND(s)) RETURN(FNM_NOMATCH); if (t = bracket(p + 1, s, flags)) { p = t; Inc(s); continue; } goto failed; } } /* ordinary */ p = UNESCAPE(p); if (ISEND(s)) RETURN(ISEND(p) ? 0 : FNM_NOMATCH); if (ISEND(p)) goto failed; if (Compare(p, s) != 0) goto failed; Inc(p); Inc(s); continue; failed: /* try next '*' position */ if (ptmp && stmp) { p = ptmp; Inc(stmp); /* !ISEND(*stmp) */ s = stmp; continue; } RETURN(FNM_NOMATCH); } } static int fnmatch(p, s, flags) const char *p; /* pattern */ const char *s; /* string */ int flags; { const int period = !(flags & FNM_DOTMATCH); const int pathname = flags & FNM_PATHNAME; const char *ptmp = 0; const char *stmp = 0; if (pathname) { while (1) { if (p[0] == '*' && p[1] == '*' && p[2] == '/') { do { p += 3; } while (p[0] == '*' && p[1] == '*' && p[2] == '/'); ptmp = p; stmp = s; } if (fnmatch_helper(&p, &s, flags) == 0) { while (*s && *s != '/') Inc(s); if (*p && *s) { p++; s++; continue; } if (!*p && !*s) return 0; } /* failed : try next recursion */ if (ptmp && stmp && !(period && *stmp == '.')) { while (*stmp && *stmp != '/') Inc(stmp); if (*stmp) { p = ptmp; stmp++; s = stmp; continue; } } return FNM_NOMATCH; } } else return fnmatch_helper(&p, &s, flags); } VALUE rb_cDir; struct dir_data { DIR *dir; char *path; }; static void free_dir(dir) struct dir_data *dir; { if (dir) { if (dir->dir) closedir(dir->dir); if (dir->path) free(dir->path); } free(dir); } static VALUE dir_close _((VALUE)); static VALUE dir_s_alloc _((VALUE)); static VALUE dir_s_alloc(klass) VALUE klass; { struct dir_data *dirp; VALUE obj = Data_Make_Struct(klass, struct dir_data, 0, free_dir, dirp); dirp->dir = NULL; dirp->path = NULL; return obj; } /* * call-seq: * Dir.new( string ) -> aDir * * Returns a new directory object for the named directory. */ static VALUE dir_initialize(dir, dirname) VALUE dir, dirname; { struct dir_data *dp; SafeStringValue(dirname); Data_Get_Struct(dir, struct dir_data, dp); if (dp->dir) closedir(dp->dir); if (dp->path) free(dp->path); dp->dir = NULL; dp->path = NULL; dp->dir = opendir(RSTRING(dirname)->ptr); if (dp->dir == NULL) { if (errno == EMFILE || errno == ENFILE) { rb_gc(); dp->dir = opendir(RSTRING(dirname)->ptr); } if (dp->dir == NULL) { rb_sys_fail(RSTRING(dirname)->ptr); } } dp->path = strdup(RSTRING(dirname)->ptr); return dir; } /* * call-seq: * Dir.open( string ) => aDir * Dir.open( string ) {| aDir | block } => anObject * * With no block, open is a synonym for * Dir::new. If a block is present, it is passed * aDir as a parameter. The directory is closed at the end of * the block, and Dir::open returns the value of the * block. */ static VALUE dir_s_open(klass, dirname) VALUE klass, dirname; { struct dir_data *dp; VALUE dir = Data_Make_Struct(klass, struct dir_data, 0, free_dir, dp); dir_initialize(dir, dirname); if (rb_block_given_p()) { return rb_ensure(rb_yield, dir, dir_close, dir); } return dir; } static void dir_closed() { rb_raise(rb_eIOError, "closed directory"); } static void dir_check(dir) VALUE dir; { if (!OBJ_TAINTED(dir) && rb_safe_level() >= 4) rb_raise(rb_eSecurityError, "Insecure: operation on untainted Dir"); rb_check_frozen(dir); } #define GetDIR(obj, dirp) do {\ dir_check(dir);\ Data_Get_Struct(obj, struct dir_data, dirp);\ if (dirp->dir == NULL) dir_closed();\ } while (0) /* * call-seq: * dir.inspect => string * * Return a string describing this Dir object. */ static VALUE dir_inspect(dir) VALUE dir; { struct dir_data *dirp; GetDIR(dir, dirp); if (dirp->path) { char *c = rb_obj_classname(dir); int len = strlen(c) + strlen(dirp->path) + 4; VALUE s = rb_str_new(0, len); snprintf(RSTRING_PTR(s), len+1, "#<%s:%s>", c, dirp->path); return s; } return rb_funcall(dir, rb_intern("to_s"), 0, 0); } /* * call-seq: * dir.path => string or nil * * Returns the path parameter passed to dir's constructor. * * d = Dir.new("..") * d.path #=> ".." */ static VALUE dir_path(dir) VALUE dir; { struct dir_data *dirp; GetDIR(dir, dirp); if (!dirp->path) return Qnil; return rb_str_new2(dirp->path); } /* * call-seq: * dir.read => string or nil * * Reads the next entry from dir and returns it as a string. * Returns nil at the end of the stream. * * d = Dir.new("testdir") * d.read #=> "." * d.read #=> ".." * d.read #=> "config.h" */ static VALUE dir_read(dir) VALUE dir; { struct dir_data *dirp; struct dirent *dp; GetDIR(dir, dirp); errno = 0; dp = readdir(dirp->dir); if (dp) { return rb_tainted_str_new(dp->d_name, NAMLEN(dp)); } else if (errno == 0) { /* end of stream */ return Qnil; } else { rb_sys_fail(0); } return Qnil; /* not reached */ } /* * call-seq: * dir.each { |filename| block } => dir * * Calls the block once for each entry in this directory, passing the * filename of each entry as a parameter to the block. * * d = Dir.new("testdir") * d.each {|x| puts "Got #{x}" } * * produces: * * Got . * Got .. * Got config.h * Got main.rb */ static VALUE dir_each(dir) VALUE dir; { struct dir_data *dirp; struct dirent *dp; GetDIR(dir, dirp); rewinddir(dirp->dir); for (dp = readdir(dirp->dir); dp != NULL; dp = readdir(dirp->dir)) { rb_yield(rb_tainted_str_new(dp->d_name, NAMLEN(dp))); if (dirp->dir == NULL) dir_closed(); } return dir; } /* * call-seq: * dir.pos => integer * dir.tell => integer * * Returns the current position in dir. See also * Dir#seek. * * d = Dir.new("testdir") * d.tell #=> 0 * d.read #=> "." * d.tell #=> 12 */ static VALUE dir_tell(dir) VALUE dir; { #ifdef HAVE_TELLDIR struct dir_data *dirp; long pos; GetDIR(dir, dirp); pos = telldir(dirp->dir); return rb_int2inum(pos); #else rb_notimplement(); #endif } /* * call-seq: * dir.seek( integer ) => dir * * Seeks to a particular location in dir. integer * must be a value returned by Dir#tell. * * d = Dir.new("testdir") #=> # * d.read #=> "." * i = d.tell #=> 12 * d.read #=> ".." * d.seek(i) #=> # * d.read #=> ".." */ static VALUE dir_seek(dir, pos) VALUE dir, pos; { struct dir_data *dirp; off_t p = NUM2OFFT(pos); GetDIR(dir, dirp); #ifdef HAVE_SEEKDIR seekdir(dirp->dir, p); return dir; #else rb_notimplement(); #endif } /* * call-seq: * dir.pos( integer ) => integer * * Synonym for Dir#seek, but returns the position * parameter. * * d = Dir.new("testdir") #=> # * d.read #=> "." * i = d.pos #=> 12 * d.read #=> ".." * d.pos = i #=> 12 * d.read #=> ".." */ static VALUE dir_set_pos(dir, pos) VALUE dir, pos; { dir_seek(dir, pos); return pos; } /* * call-seq: * dir.rewind => dir * * Repositions dir to the first entry. * * d = Dir.new("testdir") * d.read #=> "." * d.rewind #=> # * d.read #=> "." */ static VALUE dir_rewind(dir) VALUE dir; { struct dir_data *dirp; if (rb_safe_level() >= 4 && !OBJ_TAINTED(dir)) { rb_raise(rb_eSecurityError, "Insecure: can't close"); } GetDIR(dir, dirp); rewinddir(dirp->dir); return dir; } /* * call-seq: * dir.close => nil * * Closes the directory stream. Any further attempts to access * dir will raise an IOError. * * d = Dir.new("testdir") * d.close #=> nil */ static VALUE dir_close(dir) VALUE dir; { struct dir_data *dirp; GetDIR(dir, dirp); closedir(dirp->dir); dirp->dir = NULL; return Qnil; } static void dir_chdir(path) VALUE path; { if (chdir(RSTRING(path)->ptr) < 0) rb_sys_fail(RSTRING(path)->ptr); } static int chdir_blocking = 0; static VALUE chdir_thread = Qnil; struct chdir_data { VALUE old_path, new_path; int done; }; static VALUE chdir_yield(args) struct chdir_data *args; { dir_chdir(args->new_path); args->done = Qtrue; chdir_blocking++; if (chdir_thread == Qnil) chdir_thread = rb_thread_current(); return rb_yield(args->new_path); } static VALUE chdir_restore(args) struct chdir_data *args; { if (args->done) { chdir_blocking--; if (chdir_blocking == 0) chdir_thread = Qnil; dir_chdir(args->old_path); } return Qnil; } /* * call-seq: * Dir.chdir( [ string] ) => 0 * Dir.chdir( [ string] ) {| path | block } => anObject * * Changes the current working directory of the process to the given * string. When called without an argument, changes the directory to * the value of the environment variable HOME, or * LOGDIR. SystemCallError (probably * Errno::ENOENT) if the target directory does not exist. * * If a block is given, it is passed the name of the new current * directory, and the block is executed with that as the current * directory. The original working directory is restored when the block * exits. The return value of chdir is the value of the * block. chdir blocks can be nested, but in a * multi-threaded program an error will be raised if a thread attempts * to open a chdir block while another thread has one * open. * * Dir.chdir("/var/spool/mail") * puts Dir.pwd * Dir.chdir("/tmp") do * puts Dir.pwd * Dir.chdir("/usr") do * puts Dir.pwd * end * puts Dir.pwd * end * puts Dir.pwd * * produces: * * /var/spool/mail * /tmp * /usr * /tmp * /var/spool/mail */ static VALUE dir_s_chdir(argc, argv, obj) int argc; VALUE *argv; VALUE obj; { VALUE path = Qnil; rb_secure(2); if (rb_scan_args(argc, argv, "01", &path) == 1) { SafeStringValue(path); } else { const char *dist = getenv("HOME"); if (!dist) { dist = getenv("LOGDIR"); if (!dist) rb_raise(rb_eArgError, "HOME/LOGDIR not set"); } path = rb_str_new2(dist); } if (chdir_blocking > 0) { if (!rb_block_given_p() || rb_thread_current() != chdir_thread) rb_warn("conflicting chdir during another chdir block"); } if (rb_block_given_p()) { struct chdir_data args; char *cwd = my_getcwd(); args.old_path = rb_tainted_str_new2(cwd); free(cwd); args.new_path = path; args.done = Qfalse; return rb_ensure(chdir_yield, (VALUE)&args, chdir_restore, (VALUE)&args); } dir_chdir(path); return INT2FIX(0); } /* * call-seq: * Dir.getwd => string * Dir.pwd => string * * Returns the path to the current working directory of this process as * a string. * * Dir.chdir("/tmp") #=> 0 * Dir.getwd #=> "/tmp" */ static VALUE dir_s_getwd(dir) VALUE dir; { char *path; VALUE cwd; rb_secure(4); path = my_getcwd(); cwd = rb_tainted_str_new2(path); free(path); return cwd; } static void check_dirname _((volatile VALUE *)); static void check_dirname(dir) volatile VALUE *dir; { char *path, *pend; SafeStringValue(*dir); rb_secure(2); path = RSTRING(*dir)->ptr; if (path && *(pend = rb_path_end(rb_path_skip_prefix(path)))) { *dir = rb_str_new(path, pend - path); } } /* * call-seq: * Dir.chroot( string ) => 0 * * Changes this process's idea of the file system root. Only a * privileged process may make this call. Not available on all * platforms. On Unix systems, see chroot(2) for more * information. */ static VALUE dir_s_chroot(dir, path) VALUE dir, path; { #if defined(HAVE_CHROOT) && !defined(__CHECKER__) check_dirname(&path); if (chroot(RSTRING(path)->ptr) == -1) rb_sys_fail(RSTRING(path)->ptr); return INT2FIX(0); #else rb_notimplement(); return Qnil; /* not reached */ #endif } /* * call-seq: * Dir.mkdir( string [, integer] ) => 0 * * Makes a new directory named by string, with permissions * specified by the optional parameter anInteger. The * permissions may be modified by the value of * File::umask, and are ignored on NT. Raises a * SystemCallError if the directory cannot be created. See * also the discussion of permissions in the class documentation for * File. * */ static VALUE dir_s_mkdir(argc, argv, obj) int argc; VALUE *argv; VALUE obj; { VALUE path, vmode; int mode; if (rb_scan_args(argc, argv, "11", &path, &vmode) == 2) { mode = NUM2INT(vmode); } else { mode = 0777; } check_dirname(&path); if (mkdir(RSTRING(path)->ptr, mode) == -1) rb_sys_fail(RSTRING(path)->ptr); return INT2FIX(0); } /* * call-seq: * Dir.delete( string ) => 0 * Dir.rmdir( string ) => 0 * Dir.unlink( string ) => 0 * * Deletes the named directory. Raises a subclass of * SystemCallError if the directory isn't empty. */ static VALUE dir_s_rmdir(obj, dir) VALUE obj, dir; { check_dirname(&dir); if (rmdir(RSTRING(dir)->ptr) < 0) rb_sys_fail(RSTRING(dir)->ptr); return INT2FIX(0); } static void sys_warning_1(mesg) const char* mesg; { rb_sys_warning("%s", mesg); } #define GLOB_VERBOSE (1UL << (sizeof(int) * CHAR_BIT - 1)) #define sys_warning(val) \ (void)((flags & GLOB_VERBOSE) && rb_protect((VALUE (*)_((VALUE)))sys_warning_1, (VALUE)(val), 0)) #define GLOB_ALLOC(type) (type *)malloc(sizeof(type)) #define GLOB_ALLOC_N(type, n) (type *)malloc(sizeof(type) * (n)) #define GLOB_JUMP_TAG(status) ((status == -1) ? rb_memerror() : rb_jump_tag(status)) /* * ENOTDIR can be returned by stat(2) if a non-leaf element of the path * is not a directory. */ #define to_be_ignored(e) ((e) == ENOENT || (e) == ENOTDIR) /* System call with warning */ static int do_stat(const char *path, struct stat *pst, int flags) { int ret = stat(path, pst); if (ret < 0 && !to_be_ignored(errno)) sys_warning(path); return ret; } static int do_lstat(const char *path, struct stat *pst, int flags) { int ret = lstat(path, pst); if (ret < 0 && !to_be_ignored(errno)) sys_warning(path); return ret; } static DIR * do_opendir(const char *path, int flags) { DIR *dirp = opendir(path); if (dirp == NULL && !to_be_ignored(errno)) sys_warning(path); return dirp; } /* Return nonzero if S has any special globbing chars in it. */ static int has_magic(s, flags) const char *s; int flags; { const int escape = !(flags & FNM_NOESCAPE); const int nocase = flags & FNM_CASEFOLD; register const char *p = s; register char c; while (c = *p++) { switch (c) { case '*': case '?': case '[': return 1; case '\\': if (escape && !(c = *p++)) return 0; continue; default: if (!FNM_SYSCASE && ISALPHA(c) && nocase) return 1; } p = Next(p-1); } return 0; } /* Find separator in globbing pattern. */ static char * find_dirsep(const char *s, int flags) { const int escape = !(flags & FNM_NOESCAPE); register const char *p = s; register char c; int open = 0; while (c = *p++) { switch (c) { case '[': open = 1; continue; case ']': open = 0; continue; case '/': if (!open) return (char *)p-1; continue; case '\\': if (escape && !(c = *p++)) return (char *)p-1; continue; } p = Next(p-1); } return (char *)p-1; } /* Remove escaping backslashes */ static void remove_backslashes(p) char *p; { char *t = p; char *s = p; while (*p) { if (*p == '\\') { if (t != s) memmove(t, s, p - s); t += p - s; s = ++p; if (!*p) break; } Inc(p); } while (*p++); if (t != s) memmove(t, s, p - s); /* move '\0' too */ } /* Globing pattern */ enum glob_pattern_type { PLAIN, MAGICAL, RECURSIVE, MATCH_ALL, MATCH_DIR }; struct glob_pattern { char *str; enum glob_pattern_type type; struct glob_pattern *next; }; static void glob_free_pattern(struct glob_pattern *list); static struct glob_pattern * glob_make_pattern(const char *p, int flags) { struct glob_pattern *list, *tmp, **tail = &list; int dirsep = 0; /* pattern is terminated with '/' */ while (*p) { tmp = GLOB_ALLOC(struct glob_pattern); if (!tmp) goto error; if (p[0] == '*' && p[1] == '*' && p[2] == '/') { /* fold continuous RECURSIVEs (needed in glob_helper) */ do { p += 3; } while (p[0] == '*' && p[1] == '*' && p[2] == '/'); tmp->type = RECURSIVE; tmp->str = 0; dirsep = 1; } else { const char *m = find_dirsep(p, flags); char *buf = GLOB_ALLOC_N(char, m-p+1); if (!buf) { free(tmp); goto error; } memcpy(buf, p, m-p); buf[m-p] = '\0'; tmp->type = has_magic(buf, flags) ? MAGICAL : PLAIN; tmp->str = buf; if (*m) { dirsep = 1; p = m + 1; } else { dirsep = 0; p = m; } } *tail = tmp; tail = &tmp->next; } tmp = GLOB_ALLOC(struct glob_pattern); if (!tmp) { error: *tail = 0; glob_free_pattern(list); return 0; } tmp->type = dirsep ? MATCH_DIR : MATCH_ALL; tmp->str = 0; *tail = tmp; tmp->next = 0; return list; } static void glob_free_pattern(struct glob_pattern *list) { while (list) { struct glob_pattern *tmp = list; list = list->next; if (tmp->str) free(tmp->str); free(tmp); } } static char * join_path(const char *path, int dirsep, const char *name) { long len = strlen(path); char *buf = GLOB_ALLOC_N(char, len+strlen(name)+(dirsep?1:0)+1); if (!buf) return 0; memcpy(buf, path, len); if (dirsep) { strcpy(buf+len, "/"); len++; } strcpy(buf+len, name); return buf; } enum answer { YES, NO, UNKNOWN }; #ifndef S_ISLNK # ifndef S_IFLNK # define S_ISLNK(m) (0) # else # define S_ISLNK(m) ((m & S_IFMT) == S_IFLNK) # endif #endif #ifndef S_ISDIR # define S_ISDIR(m) ((m & S_IFMT) == S_IFDIR) #endif struct glob_args { void (*func) _((const char*, VALUE)); const char *c; VALUE v; }; static VALUE glob_func_caller _((VALUE)); static VALUE glob_func_caller(val) VALUE val; { struct glob_args *args = (struct glob_args *)val; (*args->func)(args->c, args->v); return Qnil; } #define glob_call_func(func, path, arg) (*func)(path, arg) static int glob_helper _((const char *, int, enum answer, enum answer, struct glob_pattern **, struct glob_pattern **, int, ruby_glob_func *, VALUE)); static int glob_helper(path, dirsep, exist, isdir, beg, end, flags, func, arg) const char *path; int dirsep; /* '/' should be placed before appending child entry's name to 'path'. */ enum answer exist; /* Does 'path' indicate an existing entry? */ enum answer isdir; /* Does 'path' indicate a directory or a symlink to a directory? */ struct glob_pattern **beg; struct glob_pattern **end; int flags; ruby_glob_func *func; VALUE arg; { struct stat st; int status = 0; struct glob_pattern **cur, **new_beg, **new_end; int plain = 0, magical = 0, recursive = 0, match_all = 0, match_dir = 0; int escape = !(flags & FNM_NOESCAPE); for (cur = beg; cur < end; ++cur) { struct glob_pattern *p = *cur; if (p->type == RECURSIVE) { recursive = 1; p = p->next; } switch (p->type) { case PLAIN: plain = 1; break; case MAGICAL: magical = 1; break; case MATCH_ALL: match_all = 1; break; case MATCH_DIR: match_dir = 1; break; case RECURSIVE: rb_bug("continuous RECURSIVEs"); } } if (*path) { if (match_all && exist == UNKNOWN) { if (do_lstat(path, &st, flags) == 0) { exist = YES; isdir = S_ISDIR(st.st_mode) ? YES : S_ISLNK(st.st_mode) ? UNKNOWN : NO; } else { exist = NO; isdir = NO; } } if (match_dir && isdir == UNKNOWN) { if (do_stat(path, &st, flags) == 0) { exist = YES; isdir = S_ISDIR(st.st_mode) ? YES : NO; } else { exist = NO; isdir = NO; } } if (match_all && exist == YES) { status = glob_call_func(func, path, arg); if (status) return status; } if (match_dir && isdir == YES) { char *tmp = join_path(path, dirsep, ""); if (!tmp) return -1; status = glob_call_func(func, tmp, arg); free(tmp); if (status) return status; } } if (exist == NO || isdir == NO) return 0; if (magical || recursive) { struct dirent *dp; DIR *dirp = do_opendir(*path ? path : ".", flags); if (dirp == NULL) return 0; for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) { char *buf = join_path(path, dirsep, dp->d_name); enum answer new_isdir = UNKNOWN; if (!buf) { status = -1; break; } if (recursive && strcmp(dp->d_name, ".") != 0 && strcmp(dp->d_name, "..") != 0 && fnmatch("*", dp->d_name, flags) == 0) { #ifndef _WIN32 if (do_lstat(buf, &st, flags) == 0) new_isdir = S_ISDIR(st.st_mode) ? YES : S_ISLNK(st.st_mode) ? UNKNOWN : NO; else new_isdir = NO; #else new_isdir = dp->d_isdir ? (!dp->d_isrep ? YES : UNKNOWN) : NO; #endif } new_beg = new_end = GLOB_ALLOC_N(struct glob_pattern *, (end - beg) * 2); if (!new_beg) { status = -1; break; } for (cur = beg; cur < end; ++cur) { struct glob_pattern *p = *cur; if (p->type == RECURSIVE) { if (new_isdir == YES) /* not symlink but real directory */ *new_end++ = p; /* append recursive pattern */ p = p->next; /* 0 times recursion */ } if (p->type == PLAIN || p->type == MAGICAL) { if (fnmatch(p->str, dp->d_name, flags) == 0) *new_end++ = p->next; } } status = glob_helper(buf, 1, YES, new_isdir, new_beg, new_end, flags, func, arg); free(buf); free(new_beg); if (status) break; } closedir(dirp); } else if (plain) { struct glob_pattern **copy_beg, **copy_end, **cur2; copy_beg = copy_end = GLOB_ALLOC_N(struct glob_pattern *, end - beg); if (!copy_beg) return -1; for (cur = beg; cur < end; ++cur) *copy_end++ = (*cur)->type == PLAIN ? *cur : 0; for (cur = copy_beg; cur < copy_end; ++cur) { if (*cur) { char *buf; char *name; name = GLOB_ALLOC_N(char, strlen((*cur)->str) + 1); if (!name) { status = -1; break; } strcpy(name, (*cur)->str); if (escape) remove_backslashes(name); new_beg = new_end = GLOB_ALLOC_N(struct glob_pattern *, end - beg); if (!new_beg) { free(name); status = -1; break; } *new_end++ = (*cur)->next; for (cur2 = cur + 1; cur2 < copy_end; ++cur2) { if (*cur2 && fnmatch((*cur2)->str, name, flags) == 0) { *new_end++ = (*cur2)->next; *cur2 = 0; } } buf = join_path(path, dirsep, name); free(name); if (!buf) { free(new_beg); status = -1; break; } status = glob_helper(buf, 1, UNKNOWN, UNKNOWN, new_beg, new_end, flags, func, arg); free(buf); free(new_beg); if (status) break; } } free(copy_beg); } return status; } static int ruby_glob0(path, flags, func, arg) const char *path; int flags; ruby_glob_func *func; VALUE arg; { struct glob_pattern *list; const char *root, *start; char *buf; int n; int status; start = root = path; flags |= FNM_SYSCASE; #if defined DOSISH root = rb_path_skip_prefix(root); #endif if (root && *root == '/') root++; n = root - start; buf = GLOB_ALLOC_N(char, n + 1); if (!buf) return -1; MEMCPY(buf, start, char, n); buf[n] = '\0'; list = glob_make_pattern(root, flags); if (!list) { free(buf); return -1; } status = glob_helper(buf, 0, UNKNOWN, UNKNOWN, &list, &list + 1, flags, func, arg); glob_free_pattern(list); free(buf); return status; } int ruby_glob(path, flags, func, arg) const char *path; int flags; ruby_glob_func *func; VALUE arg; { return ruby_glob0(path, flags & ~GLOB_VERBOSE, func, arg); } static int rb_glob_caller _((const char *, VALUE)); static int rb_glob_caller(path, a) const char *path; VALUE a; { int status; struct glob_args *args = (struct glob_args *)a; args->c = path; rb_protect(glob_func_caller, a, &status); return status; } static int rb_glob2(path, flags, func, arg) const char *path; int flags; void (*func) _((const char *, VALUE)); VALUE arg; { struct glob_args args; args.func = func; args.v = arg; if (flags & FNM_SYSCASE) { rb_warning("Dir.glob() ignores File::FNM_CASEFOLD"); } return ruby_glob0(path, flags | GLOB_VERBOSE, rb_glob_caller, (VALUE)&args); } void rb_glob(path, func, arg) const char *path; void (*func) _((const char*, VALUE)); VALUE arg; { int status = rb_glob2(path, 0, func, arg); if (status) GLOB_JUMP_TAG(status); } static void push_pattern _((const char* path, VALUE ary)); static void push_pattern(path, ary) const char *path; VALUE ary; { rb_ary_push(ary, rb_tainted_str_new2(path)); } int ruby_brace_expand(str, flags, func, arg) const char *str; int flags; ruby_glob_func *func; VALUE arg; { const int escape = !(flags & FNM_NOESCAPE); const char *p = str; const char *s = p; const char *lbrace = 0, *rbrace = 0; int nest = 0, status = 0; while (*p) { if (*p == '{' && nest++ == 0) { lbrace = p; } if (*p == '}' && --nest <= 0) { rbrace = p; break; } if (*p == '\\' && escape) { if (!*++p) break; } Inc(p); } if (lbrace && rbrace) { char *buf = GLOB_ALLOC_N(char, strlen(s) + 1); long shift; if (!buf) return -1; memcpy(buf, s, lbrace-s); shift = (lbrace-s); p = lbrace; while (p < rbrace) { const char *t = ++p; nest = 0; while (p < rbrace && !(*p == ',' && nest == 0)) { if (*p == '{') nest++; if (*p == '}') nest--; if (*p == '\\' && escape) { if (++p == rbrace) break; } Inc(p); } memcpy(buf+shift, t, p-t); strcpy(buf+shift+(p-t), rbrace+1); status = ruby_brace_expand(buf, flags, func, arg); if (status) break; } free(buf); } else if (!lbrace && !rbrace) { status = (*func)(s, arg); } return status; } struct brace_args { ruby_glob_func *func; VALUE value; int flags; }; static int glob_brace _((const char *, VALUE)); static int glob_brace(path, val) const char *path; VALUE val; { struct brace_args *arg = (struct brace_args *)val; return ruby_glob0(path, arg->flags, arg->func, arg->value); } static int ruby_brace_glob0(str, flags, func, arg) const char *str; int flags; ruby_glob_func *func; VALUE arg; { struct brace_args args; args.func = func; args.value = arg; args.flags = flags; return ruby_brace_expand(str, flags, glob_brace, (VALUE)&args); } int ruby_brace_glob(str, flags, func, arg) const char *str; int flags; ruby_glob_func *func; VALUE arg; { return ruby_brace_glob0(str, flags & ~GLOB_VERBOSE, func, arg); } static int push_glob(VALUE ary, const char *str, int flags) { struct glob_args args; args.func = push_pattern; args.v = ary; return ruby_brace_glob0(str, flags | GLOB_VERBOSE, rb_glob_caller, (VALUE)&args); } static VALUE rb_push_glob(str, flags) /* '\0' is delimiter */ VALUE str; int flags; { long offset = 0; VALUE ary; ary = rb_ary_new(); SafeStringValue(str); while (offset < RSTRING_LEN(str)) { int status = push_glob(ary, RSTRING(str)->ptr + offset, flags); char *p, *pend; if (status) GLOB_JUMP_TAG(status); if (offset >= RSTRING_LEN(str)) break; p = RSTRING(str)->ptr + offset; p += strlen(p) + 1; pend = RSTRING(str)->ptr + RSTRING_LEN(str); while (p < pend && !*p) p++; offset = p - RSTRING(str)->ptr; } return ary; } static VALUE dir_globs(argc, argv, flags) long argc; VALUE *argv; int flags; { VALUE ary = rb_ary_new(); long i; for (i = 0; i < argc; ++i) { int status; VALUE str = argv[i]; SafeStringValue(str); status = push_glob(ary, RSTRING(str)->ptr, flags); if (status) GLOB_JUMP_TAG(status); } return ary; } /* * call-seq: * Dir[ array ] => array * Dir[ string [, string ...] ] => array * * Equivalent to calling * Dir.glob(array,0) and * Dir.glob([string,...],0). * */ static VALUE dir_s_aref(int argc, VALUE *argv, VALUE obj) { if (argc == 1) { return rb_push_glob(argv[0], 0); } return dir_globs(argc, argv, 0); } /* * call-seq: * Dir.glob( pattern, [flags] ) => array * Dir.glob( pattern, [flags] ) {| filename | block } => nil * * Returns the filenames found by expanding pattern which is * an +Array+ of the patterns or the pattern +String+, either as an * array or as parameters to the block. Note that this pattern * is not a regexp (it's closer to a shell glob). See * File::fnmatch for the meaning of the flags * parameter. Note that case sensitivity depends on your system (so * File::FNM_CASEFOLD is ignored) * * *:: Matches any file. Can be restricted by * other values in the glob. * * will match all files; c* will * match all files beginning with * c; *c will match * all files ending with c; and * *c* will match all files that * have c in them (including at * the beginning or end). Equivalent to * / .* /x in regexp. * **:: Matches directories recursively. * ?:: Matches any one character. Equivalent to * /.{1}/ in regexp. * [set]:: Matches any one character in +set+. * Behaves exactly like character sets in * Regexp, including set negation * ([^a-z]). * {p,q}:: Matches either literal p or * literal q. Matching literals * may be more than one character in length. * More than two literals may be specified. * Equivalent to pattern alternation in * regexp. * \:: Escapes the next metacharacter. * * Dir["config.?"] #=> ["config.h"] * Dir.glob("config.?") #=> ["config.h"] * Dir.glob("*.[a-z][a-z]") #=> ["main.rb"] * Dir.glob("*.[^r]*") #=> ["config.h"] * Dir.glob("*.{rb,h}") #=> ["main.rb", "config.h"] * Dir.glob("*") #=> ["config.h", "main.rb"] * Dir.glob("*", File::FNM_DOTMATCH) #=> [".", "..", "config.h", "main.rb"] * * rbfiles = File.join("**", "*.rb") * Dir.glob(rbfiles) #=> ["main.rb", * "lib/song.rb", * "lib/song/karaoke.rb"] * libdirs = File.join("**", "lib") * Dir.glob(libdirs) #=> ["lib"] * * librbfiles = File.join("**", "lib", "**", "*.rb") * Dir.glob(librbfiles) #=> ["lib/song.rb", * "lib/song/karaoke.rb"] * * librbfiles = File.join("**", "lib", "*.rb") * Dir.glob(librbfiles) #=> ["lib/song.rb"] */ static VALUE dir_s_glob(argc, argv, obj) int argc; VALUE *argv; VALUE obj; { VALUE str, rflags, ary; int flags; if (rb_scan_args(argc, argv, "11", &str, &rflags) == 2) flags = NUM2INT(rflags); else flags = 0; ary = rb_check_array_type(str); if (NIL_P(ary)) { ary = rb_push_glob(str, flags); } else { volatile VALUE v = ary; ary = dir_globs(RARRAY_LEN(v), RARRAY_PTR(v), flags); } if (rb_block_given_p()) { rb_ary_each(ary); return Qnil; } return ary; } static VALUE dir_open_dir(path) VALUE path; { VALUE dir = rb_funcall(rb_cDir, rb_intern("open"), 1, path); if (TYPE(dir) != T_DATA || RDATA(dir)->dfree != (RUBY_DATA_FUNC)free_dir) { rb_raise(rb_eTypeError, "wrong argument type %s (expected Dir)", rb_obj_classname(dir)); } return dir; } /* * call-seq: * Dir.foreach( dirname ) {| filename | block } => nil * * Calls the block once for each entry in the named directory, passing * the filename of each entry as a parameter to the block. * * Dir.foreach("testdir") {|x| puts "Got #{x}" } * * produces: * * Got . * Got .. * Got config.h * Got main.rb * */ static VALUE dir_foreach(io, dirname) VALUE io, dirname; { VALUE dir; dir = dir_open_dir(dirname); rb_ensure(dir_each, dir, dir_close, dir); return Qnil; } /* * call-seq: * Dir.entries( dirname ) => array * * Returns an array containing all of the filenames in the given * directory. Will raise a SystemCallError if the named * directory doesn't exist. * * Dir.entries("testdir") #=> [".", "..", "config.h", "main.rb"] * */ static VALUE dir_entries(io, dirname) VALUE io, dirname; { VALUE dir; dir = dir_open_dir(dirname); return rb_ensure(rb_Array, dir, dir_close, dir); } /* * call-seq: * File.fnmatch( pattern, path, [flags] ) => (true or false) * File.fnmatch?( pattern, path, [flags] ) => (true or false) * * Returns true if path matches against pattern The * pattern is not a regular expression; instead it follows rules * similar to shell filename globbing. It may contain the following * metacharacters: * * *:: Matches any file. Can be restricted by * other values in the glob. * * will match all files; c* will * match all files beginning with * c; *c will match * all files ending with c; and * *c* will match all files that * have c in them (including at * the beginning or end). Equivalent to * / .* /x in regexp. * **:: Matches directories recursively or files * expansively. * ?:: Matches any one character. Equivalent to * /.{1}/ in regexp. * [set]:: Matches any one character in +set+. * Behaves exactly like character sets in * Regexp, including set negation * ([^a-z]). * \:: Escapes the next metacharacter. * * flags is a bitwise OR of the FNM_xxx * parameters. The same glob pattern and flags are used by * Dir::glob. * * File.fnmatch('cat', 'cat') #=> true : match entire string * File.fnmatch('cat', 'category') #=> false : only match partial string * File.fnmatch('c{at,ub}s', 'cats') #=> false : { } isn't supported * * File.fnmatch('c?t', 'cat') #=> true : '?' match only 1 character * File.fnmatch('c??t', 'cat') #=> false : ditto * File.fnmatch('c*', 'cats') #=> true : '*' match 0 or more characters * File.fnmatch('c*t', 'c/a/b/t') #=> true : ditto * File.fnmatch('ca[a-z]', 'cat') #=> true : inclusive bracket expression * File.fnmatch('ca[^t]', 'cat') #=> false : exclusive bracket expression ('^' or '!') * * File.fnmatch('cat', 'CAT') #=> false : case sensitive * File.fnmatch('cat', 'CAT', File::FNM_CASEFOLD) #=> true : case insensitive * * File.fnmatch('?', '/', File::FNM_PATHNAME) #=> false : wildcard doesn't match '/' on FNM_PATHNAME * File.fnmatch('*', '/', File::FNM_PATHNAME) #=> false : ditto * File.fnmatch('[/]', '/', File::FNM_PATHNAME) #=> false : ditto * * File.fnmatch('\?', '?') #=> true : escaped wildcard becomes ordinary * File.fnmatch('\a', 'a') #=> true : escaped ordinary remains ordinary * File.fnmatch('\a', '\a', File::FNM_NOESCAPE) #=> true : FNM_NOESACPE makes '\' ordinary * File.fnmatch('[\?]', '?') #=> true : can escape inside bracket expression * * File.fnmatch('*', '.profile') #=> false : wildcard doesn't match leading * File.fnmatch('*', '.profile', File::FNM_DOTMATCH) #=> true period by default. * File.fnmatch('.*', '.profile') #=> true * * rbfiles = '**' '/' '*.rb' # you don't have to do like this. just write in single string. * File.fnmatch(rbfiles, 'main.rb') #=> false * File.fnmatch(rbfiles, './main.rb') #=> false * File.fnmatch(rbfiles, 'lib/song.rb') #=> true * File.fnmatch('**.rb', 'main.rb') #=> true * File.fnmatch('**.rb', './main.rb') #=> false * File.fnmatch('**.rb', 'lib/song.rb') #=> true * File.fnmatch('*', 'dave/.profile') #=> true * * pattern = '*' '/' '*' * File.fnmatch(pattern, 'dave/.profile', File::FNM_PATHNAME) #=> false * File.fnmatch(pattern, 'dave/.profile', File::FNM_PATHNAME | File::FNM_DOTMATCH) #=> true * * pattern = '**' '/' 'foo' * File.fnmatch(pattern, 'a/b/c/foo', File::FNM_PATHNAME) #=> true * File.fnmatch(pattern, '/a/b/c/foo', File::FNM_PATHNAME) #=> true * File.fnmatch(pattern, 'c:/a/b/c/foo', File::FNM_PATHNAME) #=> true * File.fnmatch(pattern, 'a/.b/c/foo', File::FNM_PATHNAME) #=> false * File.fnmatch(pattern, 'a/.b/c/foo', File::FNM_PATHNAME | File::FNM_DOTMATCH) #=> true */ static VALUE file_s_fnmatch(argc, argv, obj) int argc; VALUE *argv; VALUE obj; { VALUE pattern, path; VALUE rflags; int flags; if (rb_scan_args(argc, argv, "21", &pattern, &path, &rflags) == 3) flags = NUM2INT(rflags); else flags = 0; StringValue(pattern); StringValue(path); if (fnmatch(RSTRING(pattern)->ptr, RSTRING(path)->ptr, flags) == 0) return Qtrue; return Qfalse; } /* * Objects of class Dir are directory streams representing * directories in the underlying file system. They provide a variety of * ways to list directories and their contents. See also * File. * * The directory used in these examples contains the two regular files * (config.h and main.rb), the parent * directory (..), and the directory itself * (.). */ void Init_Dir() { rb_cDir = rb_define_class("Dir", rb_cObject); rb_include_module(rb_cDir, rb_mEnumerable); rb_define_alloc_func(rb_cDir, dir_s_alloc); rb_define_singleton_method(rb_cDir, "open", dir_s_open, 1); rb_define_singleton_method(rb_cDir, "foreach", dir_foreach, 1); rb_define_singleton_method(rb_cDir, "entries", dir_entries, 1); rb_define_method(rb_cDir,"initialize", dir_initialize, 1); rb_define_method(rb_cDir,"path", dir_path, 0); rb_define_method(rb_cDir,"read", dir_read, 0); rb_define_method(rb_cDir,"each", dir_each, 0); rb_define_method(rb_cDir,"rewind", dir_rewind, 0); rb_define_method(rb_cDir,"tell", dir_tell, 0); rb_define_method(rb_cDir,"seek", dir_seek, 1); rb_define_method(rb_cDir,"pos", dir_tell, 0); rb_define_method(rb_cDir,"pos=", dir_set_pos, 1); rb_define_method(rb_cDir,"close", dir_close, 0); rb_define_singleton_method(rb_cDir,"chdir", dir_s_chdir, -1); rb_define_singleton_method(rb_cDir,"getwd", dir_s_getwd, 0); rb_define_singleton_method(rb_cDir,"pwd", dir_s_getwd, 0); rb_define_singleton_method(rb_cDir,"chroot", dir_s_chroot, 1); rb_define_singleton_method(rb_cDir,"mkdir", dir_s_mkdir, -1); rb_define_singleton_method(rb_cDir,"rmdir", dir_s_rmdir, 1); rb_define_singleton_method(rb_cDir,"delete", dir_s_rmdir, 1); rb_define_singleton_method(rb_cDir,"unlink", dir_s_rmdir, 1); rb_define_singleton_method(rb_cDir,"glob", dir_s_glob, -1); rb_define_singleton_method(rb_cDir,"[]", dir_s_aref, -1); rb_define_singleton_method(rb_cFile,"fnmatch", file_s_fnmatch, -1); rb_define_singleton_method(rb_cFile,"fnmatch?", file_s_fnmatch, -1); rb_file_const("FNM_NOESCAPE", INT2FIX(FNM_NOESCAPE)); rb_file_const("FNM_PATHNAME", INT2FIX(FNM_PATHNAME)); rb_file_const("FNM_DOTMATCH", INT2FIX(FNM_DOTMATCH)); rb_file_const("FNM_CASEFOLD", INT2FIX(FNM_CASEFOLD)); rb_file_const("FNM_SYSCASE", INT2FIX(FNM_SYSCASE)); } /********************************************************************** dln.c - $Author: shyouhei $ $Date: 2008-06-15 15:25:08 +0200 (Sun, 15 Jun 2008) $ created at: Tue Jan 18 17:05:06 JST 1994 Copyright (C) 1993-2003 Yukihiro Matsumoto **********************************************************************/ #include "ruby.h" #include "dln.h" #ifdef HAVE_STDLIB_H # include #endif #ifdef __CHECKER__ #undef HAVE_DLOPEN #undef USE_DLN_A_OUT #undef USE_DLN_DLOPEN #endif #ifdef USE_DLN_A_OUT char *dln_argv0; #endif #if defined(HAVE_ALLOCA_H) #include #endif #ifdef HAVE_STRING_H # include #else # include #endif #ifndef xmalloc void *xmalloc(); void *xcalloc(); void *xrealloc(); #endif #include #if defined(_WIN32) || defined(__VMS) #include "missing/file.h" #endif #include #include #ifndef S_ISDIR # define S_ISDIR(m) ((m & S_IFMT) == S_IFDIR) #endif #ifdef HAVE_SYS_PARAM_H # include #endif #ifndef MAXPATHLEN # define MAXPATHLEN 1024 #endif #ifdef HAVE_UNISTD_H # include #endif #ifndef _WIN32 char *getenv(); #endif #if defined(__VMS) #pragma builtins #include #endif #ifdef __MACOS__ # include # include # include # include "macruby_private.h" #endif #ifdef __BEOS__ # include #endif #ifndef NO_DLN_LOAD #if defined(HAVE_DLOPEN) && !defined(USE_DLN_A_OUT) && !defined(_AIX) && !defined(__APPLE__) && !defined(_UNICOSMP) /* dynamic load with dlopen() */ # define USE_DLN_DLOPEN #endif #ifndef FUNCNAME_PATTERN # if defined(__hp9000s300) || (defined(__NetBSD__) && !defined(__ELF__)) || defined(__BORLANDC__) || (defined(__FreeBSD__) && !defined(__ELF__)) || (defined(__OpenBSD__) && !defined(__ELF__)) || defined(NeXT) || defined(__WATCOMC__) || defined(__APPLE__) # define FUNCNAME_PATTERN "_Init_%s" # else # define FUNCNAME_PATTERN "Init_%s" # endif #endif static int init_funcname_len(buf, file) char **buf; const char *file; { char *p; const char *slash; int len; /* Load the file as an object one */ for (slash = file-1; *file; file++) /* Find position of last '/' */ #ifdef __MACOS__ if (*file == ':') slash = file; #else if (*file == '/') slash = file; #endif len = strlen(FUNCNAME_PATTERN) + strlen(slash + 1); *buf = xmalloc(len); snprintf(*buf, len, FUNCNAME_PATTERN, slash + 1); for (p = *buf; *p; p++) { /* Delete suffix if it exists */ if (*p == '.') { *p = '\0'; break; } } return p - *buf; } #define init_funcname(buf, file) do {\ int len = init_funcname_len(buf, file);\ char *tmp = ALLOCA_N(char, len+1);\ if (!tmp) {\ free(*buf);\ rb_memerror();\ }\ strcpy(tmp, *buf);\ free(*buf);\ *buf = tmp;\ } while (0) #ifdef USE_DLN_A_OUT #ifndef LIBC_NAME # define LIBC_NAME "libc.a" #endif #ifndef DLN_DEFAULT_LIB_PATH # define DLN_DEFAULT_LIB_PATH "/lib:/usr/lib:/usr/local/lib:." #endif #include static int dln_errno; #define DLN_ENOEXEC ENOEXEC /* Exec format error */ #define DLN_ECONFL 1201 /* Symbol name conflict */ #define DLN_ENOINIT 1202 /* No initializer given */ #define DLN_EUNDEF 1203 /* Undefine symbol remains */ #define DLN_ENOTLIB 1204 /* Not a library file */ #define DLN_EBADLIB 1205 /* Malformed library file */ #define DLN_EINIT 1206 /* Not initialized */ static int dln_init_p = 0; #include #include #ifndef N_COMM # define N_COMM 0x12 #endif #ifndef N_MAGIC # define N_MAGIC(x) (x).a_magic #endif #define INVALID_OBJECT(h) (N_MAGIC(h) != OMAGIC) #include "util.h" #include "st.h" static st_table *sym_tbl; static st_table *undef_tbl; static int load_lib(); static int load_header(fd, hdrp, disp) int fd; struct exec *hdrp; long disp; { int size; lseek(fd, disp, 0); size = read(fd, hdrp, sizeof(struct exec)); if (size == -1) { dln_errno = errno; return -1; } if (size != sizeof(struct exec) || N_BADMAG(*hdrp)) { dln_errno = DLN_ENOEXEC; return -1; } return 0; } #if defined(sequent) #define RELOC_SYMBOL(r) ((r)->r_symbolnum) #define RELOC_MEMORY_SUB_P(r) ((r)->r_bsr) #define RELOC_PCREL_P(r) ((r)->r_pcrel || (r)->r_bsr) #define RELOC_TARGET_SIZE(r) ((r)->r_length) #endif /* Default macros */ #ifndef RELOC_ADDRESS #define RELOC_ADDRESS(r) ((r)->r_address) #define RELOC_EXTERN_P(r) ((r)->r_extern) #define RELOC_SYMBOL(r) ((r)->r_symbolnum) #define RELOC_MEMORY_SUB_P(r) 0 #define RELOC_PCREL_P(r) ((r)->r_pcrel) #define RELOC_TARGET_SIZE(r) ((r)->r_length) #endif #if defined(sun) && defined(sparc) /* Sparc (Sun 4) macros */ # undef relocation_info # define relocation_info reloc_info_sparc # define R_RIGHTSHIFT(r) (reloc_r_rightshift[(r)->r_type]) # define R_BITSIZE(r) (reloc_r_bitsize[(r)->r_type]) # define R_LENGTH(r) (reloc_r_length[(r)->r_type]) static int reloc_r_rightshift[] = { 0, 0, 0, 0, 0, 0, 2, 2, 10, 0, 0, 0, 0, 0, 0, }; static int reloc_r_bitsize[] = { 8, 16, 32, 8, 16, 32, 30, 22, 22, 22, 13, 10, 32, 32, 16, }; static int reloc_r_length[] = { 0, 1, 2, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, }; # define R_PCREL(r) \ ((r)->r_type >= RELOC_DISP8 && (r)->r_type <= RELOC_WDISP22) # define R_SYMBOL(r) ((r)->r_index) #endif #if defined(sequent) #define R_SYMBOL(r) ((r)->r_symbolnum) #define R_MEMORY_SUB(r) ((r)->r_bsr) #define R_PCREL(r) ((r)->r_pcrel || (r)->r_bsr) #define R_LENGTH(r) ((r)->r_length) #endif #ifndef R_SYMBOL # define R_SYMBOL(r) ((r)->r_symbolnum) # define R_MEMORY_SUB(r) 0 # define R_PCREL(r) ((r)->r_pcrel) # define R_LENGTH(r) ((r)->r_length) #endif static struct relocation_info * load_reloc(fd, hdrp, disp) int fd; struct exec *hdrp; long disp; { struct relocation_info *reloc; int size; lseek(fd, disp + N_TXTOFF(*hdrp) + hdrp->a_text + hdrp->a_data, 0); size = hdrp->a_trsize + hdrp->a_drsize; reloc = (struct relocation_info*)xmalloc(size); if (reloc == NULL) { dln_errno = errno; return NULL; } if (read(fd, reloc, size) != size) { dln_errno = errno; free(reloc); return NULL; } return reloc; } static struct nlist * load_sym(fd, hdrp, disp) int fd; struct exec *hdrp; long disp; { struct nlist * buffer; struct nlist * sym; struct nlist * end; long displ; int size; lseek(fd, N_SYMOFF(*hdrp) + hdrp->a_syms + disp, 0); if (read(fd, &size, sizeof(int)) != sizeof(int)) { goto err_noexec; } buffer = (struct nlist*)xmalloc(hdrp->a_syms + size); if (buffer == NULL) { dln_errno = errno; return NULL; } lseek(fd, disp + N_SYMOFF(*hdrp), 0); if (read(fd, buffer, hdrp->a_syms + size) != hdrp->a_syms + size) { free(buffer); goto err_noexec; } sym = buffer; end = sym + hdrp->a_syms / sizeof(struct nlist); displ = (long)buffer + (long)(hdrp->a_syms); while (sym < end) { sym->n_un.n_name = (char*)sym->n_un.n_strx + displ; sym++; } return buffer; err_noexec: dln_errno = DLN_ENOEXEC; return NULL; } static st_table * sym_hash(hdrp, syms) struct exec *hdrp; struct nlist *syms; { st_table *tbl; struct nlist *sym = syms; struct nlist *end = syms + (hdrp->a_syms / sizeof(struct nlist)); tbl = st_init_strtable(); if (tbl == NULL) { dln_errno = errno; return NULL; } while (sym < end) { st_insert(tbl, sym->n_un.n_name, sym); sym++; } return tbl; } static int dln_init(prog) const char *prog; { char *file; int fd; struct exec hdr; struct nlist *syms; if (dln_init_p == 1) return 0; file = dln_find_exe(prog, NULL); if (file == NULL || (fd = open(file, O_RDONLY)) < 0) { dln_errno = errno; return -1; } if (load_header(fd, &hdr, 0) == -1) return -1; syms = load_sym(fd, &hdr, 0); if (syms == NULL) { close(fd); return -1; } sym_tbl = sym_hash(&hdr, syms); if (sym_tbl == NULL) { /* file may be start with #! */ char c = '\0'; char buf[MAXPATHLEN]; char *p; free(syms); lseek(fd, 0L, 0); if (read(fd, &c, 1) == -1) { dln_errno = errno; return -1; } if (c != '#') goto err_noexec; if (read(fd, &c, 1) == -1) { dln_errno = errno; return -1; } if (c != '!') goto err_noexec; p = buf; /* skip forwarding spaces */ while (read(fd, &c, 1) == 1) { if (c == '\n') goto err_noexec; if (c != '\t' && c != ' ') { *p++ = c; break; } } /* read in command name */ while (read(fd, p, 1) == 1) { if (*p == '\n' || *p == '\t' || *p == ' ') break; p++; if (p-buf >= MAXPATHLEN) { dln_errno = ENAMETOOLONG; return -1; } } *p = '\0'; return dln_init(buf); } dln_init_p = 1; undef_tbl = st_init_strtable(); close(fd); return 0; err_noexec: close(fd); dln_errno = DLN_ENOEXEC; return -1; } static long load_text_data(fd, hdrp, bss, disp) int fd; struct exec *hdrp; int bss; long disp; { int size; unsigned char* addr; lseek(fd, disp + N_TXTOFF(*hdrp), 0); size = hdrp->a_text + hdrp->a_data; if (bss == -1) size += hdrp->a_bss; else if (bss > 1) size += bss; addr = (unsigned char*)xmalloc(size); if (addr == NULL) { dln_errno = errno; return 0; } if (read(fd, addr, size) != size) { dln_errno = errno; free(addr); return 0; } if (bss == -1) { memset(addr + hdrp->a_text + hdrp->a_data, 0, hdrp->a_bss); } else if (bss > 0) { memset(addr + hdrp->a_text + hdrp->a_data, 0, bss); } return (long)addr; } static int undef_print(key, value) char *key, *value; { fprintf(stderr, " %s\n", key); return ST_CONTINUE; } static void dln_print_undef() { fprintf(stderr, " Undefined symbols:\n"); st_foreach(undef_tbl, undef_print, NULL); } static void dln_undefined() { if (undef_tbl->num_entries > 0) { fprintf(stderr, "dln: Calling undefined function\n"); dln_print_undef(); rb_exit(1); } } struct undef { char *name; struct relocation_info reloc; long base; char *addr; union { char c; short s; long l; } u; }; static st_table *reloc_tbl = NULL; static void link_undef(name, base, reloc) const char *name; long base; struct relocation_info *reloc; { static int u_no = 0; struct undef *obj; char *addr = (char*)(reloc->r_address + base); obj = (struct undef*)xmalloc(sizeof(struct undef)); obj->name = strdup(name); obj->reloc = *reloc; obj->base = base; switch (R_LENGTH(reloc)) { case 0: /* byte */ obj->u.c = *addr; break; case 1: /* word */ obj->u.s = *(short*)addr; break; case 2: /* long */ obj->u.l = *(long*)addr; break; } if (reloc_tbl == NULL) { reloc_tbl = st_init_numtable(); } st_insert(reloc_tbl, u_no++, obj); } struct reloc_arg { const char *name; long value; }; static int reloc_undef(no, undef, arg) int no; struct undef *undef; struct reloc_arg *arg; { int datum; char *address; #if defined(sun) && defined(sparc) unsigned int mask = 0; #endif if (strcmp(arg->name, undef->name) != 0) return ST_CONTINUE; address = (char*)(undef->base + undef->reloc.r_address); datum = arg->value; if (R_PCREL(&(undef->reloc))) datum -= undef->base; #if defined(sun) && defined(sparc) datum += undef->reloc.r_addend; datum >>= R_RIGHTSHIFT(&(undef->reloc)); mask = (1 << R_BITSIZE(&(undef->reloc))) - 1; mask |= mask -1; datum &= mask; switch (R_LENGTH(&(undef->reloc))) { case 0: *address = undef->u.c; *address &= ~mask; *address |= datum; break; case 1: *(short *)address = undef->u.s; *(short *)address &= ~mask; *(short *)address |= datum; break; case 2: *(long *)address = undef->u.l; *(long *)address &= ~mask; *(long *)address |= datum; break; } #else switch (R_LENGTH(&(undef->reloc))) { case 0: /* byte */ if (R_MEMORY_SUB(&(undef->reloc))) *address = datum - *address; else *address = undef->u.c + datum; break; case 1: /* word */ if (R_MEMORY_SUB(&(undef->reloc))) *(short*)address = datum - *(short*)address; else *(short*)address = undef->u.s + datum; break; case 2: /* long */ if (R_MEMORY_SUB(&(undef->reloc))) *(long*)address = datum - *(long*)address; else *(long*)address = undef->u.l + datum; break; } #endif free(undef->name); free(undef); return ST_DELETE; } static void unlink_undef(name, value) const char *name; long value; { struct reloc_arg arg; arg.name = name; arg.value = value; st_foreach(reloc_tbl, reloc_undef, &arg); } #ifdef N_INDR struct indr_data { char *name0, *name1; }; static int reloc_repl(no, undef, data) int no; struct undef *undef; struct indr_data *data; { if (strcmp(data->name0, undef->name) == 0) { free(undef->name); undef->name = strdup(data->name1); } return ST_CONTINUE; } #endif static int load_1(fd, disp, need_init) int fd; long disp; const char *need_init; { static char *libc = LIBC_NAME; struct exec hdr; struct relocation_info *reloc = NULL; long block = 0; long new_common = 0; /* Length of new common */ struct nlist *syms = NULL; struct nlist *sym; struct nlist *end; int init_p = 0; if (load_header(fd, &hdr, disp) == -1) return -1; if (INVALID_OBJECT(hdr)) { dln_errno = DLN_ENOEXEC; return -1; } reloc = load_reloc(fd, &hdr, disp); if (reloc == NULL) return -1; syms = load_sym(fd, &hdr, disp); if (syms == NULL) { free(reloc); return -1; } sym = syms; end = syms + (hdr.a_syms / sizeof(struct nlist)); while (sym < end) { struct nlist *old_sym; int value = sym->n_value; #ifdef N_INDR if (sym->n_type == (N_INDR | N_EXT)) { char *key = sym->n_un.n_name; if (st_lookup(sym_tbl, sym[1].n_un.n_name, &old_sym)) { if (st_delete(undef_tbl, (st_data_t*)&key, NULL)) { unlink_undef(key, old_sym->n_value); free(key); } } else { struct indr_data data; data.name0 = sym->n_un.n_name; data.name1 = sym[1].n_un.n_name; st_foreach(reloc_tbl, reloc_repl, &data); st_insert(undef_tbl, strdup(sym[1].n_un.n_name), NULL); if (st_delete(undef_tbl, (st_data_t*)&key, NULL)) { free(key); } } sym += 2; continue; } #endif if (sym->n_type == (N_UNDF | N_EXT)) { if (st_lookup(sym_tbl, sym->n_un.n_name, &old_sym) == 0) { old_sym = NULL; } if (value) { if (old_sym) { sym->n_type = N_EXT | N_COMM; sym->n_value = old_sym->n_value; } else { int rnd = value >= sizeof(double) ? sizeof(double) - 1 : value >= sizeof(long) ? sizeof(long) - 1 : sizeof(short) - 1; sym->n_type = N_COMM; new_common += rnd; new_common &= ~(long)rnd; sym->n_value = new_common; new_common += value; } } else { if (old_sym) { sym->n_type = N_EXT | N_COMM; sym->n_value = old_sym->n_value; } else { sym->n_value = (long)dln_undefined; st_insert(undef_tbl, strdup(sym->n_un.n_name), NULL); } } } sym++; } block = load_text_data(fd, &hdr, hdr.a_bss + new_common, disp); if (block == 0) goto err_exit; sym = syms; while (sym < end) { struct nlist *new_sym; char *key; switch (sym->n_type) { case N_COMM: sym->n_value += hdr.a_text + hdr.a_data; case N_TEXT|N_EXT: case N_DATA|N_EXT: sym->n_value += block; if (st_lookup(sym_tbl, sym->n_un.n_name, &new_sym) != 0 && new_sym->n_value != (long)dln_undefined) { dln_errno = DLN_ECONFL; goto err_exit; } key = sym->n_un.n_name; if (st_delete(undef_tbl, (st_data_t*)&key, NULL) != 0) { unlink_undef(key, sym->n_value); free(key); } new_sym = (struct nlist*)xmalloc(sizeof(struct nlist)); *new_sym = *sym; new_sym->n_un.n_name = strdup(sym->n_un.n_name); st_insert(sym_tbl, new_sym->n_un.n_name, new_sym); break; case N_TEXT: case N_DATA: sym->n_value += block; break; } sym++; } /* * First comes the text-relocation */ { struct relocation_info * rel = reloc; struct relocation_info * rel_beg = reloc + (hdr.a_trsize/sizeof(struct relocation_info)); struct relocation_info * rel_end = reloc + (hdr.a_trsize+hdr.a_drsize)/sizeof(struct relocation_info); while (rel < rel_end) { char *address = (char*)(rel->r_address + block); long datum = 0; #if defined(sun) && defined(sparc) unsigned int mask = 0; #endif if(rel >= rel_beg) address += hdr.a_text; if (rel->r_extern) { /* Look it up in symbol-table */ sym = &(syms[R_SYMBOL(rel)]); switch (sym->n_type) { case N_EXT|N_UNDF: link_undef(sym->n_un.n_name, block, rel); case N_EXT|N_COMM: case N_COMM: datum = sym->n_value; break; default: goto err_exit; } } /* end.. look it up */ else { /* is static */ switch (R_SYMBOL(rel)) { case N_TEXT: case N_DATA: datum = block; break; case N_BSS: datum = block + new_common; break; case N_ABS: break; } } /* end .. is static */ if (R_PCREL(rel)) datum -= block; #if defined(sun) && defined(sparc) datum += rel->r_addend; datum >>= R_RIGHTSHIFT(rel); mask = (1 << R_BITSIZE(rel)) - 1; mask |= mask -1; datum &= mask; switch (R_LENGTH(rel)) { case 0: *address &= ~mask; *address |= datum; break; case 1: *(short *)address &= ~mask; *(short *)address |= datum; break; case 2: *(long *)address &= ~mask; *(long *)address |= datum; break; } #else switch (R_LENGTH(rel)) { case 0: /* byte */ if (datum < -128 || datum > 127) goto err_exit; *address += datum; break; case 1: /* word */ *(short *)address += datum; break; case 2: /* long */ *(long *)address += datum; break; } #endif rel++; } } if (need_init) { int len; char **libs_to_be_linked = 0; char *buf; if (undef_tbl->num_entries > 0) { if (load_lib(libc) == -1) goto err_exit; } init_funcname(&buf, need_init); len = strlen(buf); for (sym = syms; symn_un.n_name; if (name[0] == '_' && sym->n_value >= block) { if (strcmp(name+1, "dln_libs_to_be_linked") == 0) { libs_to_be_linked = (char**)sym->n_value; } else if (strcmp(name+1, buf) == 0) { init_p = 1; ((int (*)())sym->n_value)(); } } } if (libs_to_be_linked && undef_tbl->num_entries > 0) { while (*libs_to_be_linked) { load_lib(*libs_to_be_linked); libs_to_be_linked++; } } } free(reloc); free(syms); if (need_init) { if (init_p == 0) { dln_errno = DLN_ENOINIT; return -1; } if (undef_tbl->num_entries > 0) { if (load_lib(libc) == -1) goto err_exit; if (undef_tbl->num_entries > 0) { dln_errno = DLN_EUNDEF; return -1; } } } return 0; err_exit: if (syms) free(syms); if (reloc) free(reloc); if (block) free((char*)block); return -1; } static int target_offset; static int search_undef(key, value, lib_tbl) const char *key; int value; st_table *lib_tbl; { long offset; if (st_lookup(lib_tbl, key, &offset) == 0) return ST_CONTINUE; target_offset = offset; return ST_STOP; } struct symdef { int rb_str_index; int lib_offset; }; char *dln_librrb_ary_path = DLN_DEFAULT_LIB_PATH; static int load_lib(lib) const char *lib; { char *path, *file; char armagic[SARMAG]; int fd, size; struct ar_hdr ahdr; st_table *lib_tbl = NULL; int *data, nsym; struct symdef *base; char *name_base; if (dln_init_p == 0) { dln_errno = DLN_ENOINIT; return -1; } if (undef_tbl->num_entries == 0) return 0; dln_errno = DLN_EBADLIB; if (lib[0] == '-' && lib[1] == 'l') { long len = strlen(lib) + 4; char *p = alloca(len); snprintf(p, len, "lib%s.a", lib+2); lib = p; } /* library search path: */ /* look for environment variable DLN_LIBRARY_PATH first. */ /* then variable dln_librrb_ary_path. */ /* if path is still NULL, use "." for path. */ path = getenv("DLN_LIBRARY_PATH"); if (path == NULL) path = dln_librrb_ary_path; file = dln_find_file(lib, path); fd = open(file, O_RDONLY); if (fd == -1) goto syserr; size = read(fd, armagic, SARMAG); if (size == -1) goto syserr; if (size != SARMAG) { dln_errno = DLN_ENOTLIB; goto badlib; } size = read(fd, &ahdr, sizeof(ahdr)); if (size == -1) goto syserr; if (size != sizeof(ahdr) || sscanf(ahdr.ar_size, "%d", &size) != 1) { goto badlib; } if (strncmp(ahdr.ar_name, "__.SYMDEF", 9) == 0) { /* make hash table from __.SYMDEF */ lib_tbl = st_init_strtable(); data = (int*)xmalloc(size); if (data == NULL) goto syserr; size = read(fd, data, size); nsym = *data / sizeof(struct symdef); base = (struct symdef*)(data + 1); name_base = (char*)(base + nsym) + sizeof(int); while (nsym > 0) { char *name = name_base + base->rb_str_index; st_insert(lib_tbl, name, base->lib_offset + sizeof(ahdr)); nsym--; base++; } for (;;) { target_offset = -1; st_foreach(undef_tbl, search_undef, lib_tbl); if (target_offset == -1) break; if (load_1(fd, target_offset, 0) == -1) { st_free_table(lib_tbl); free(data); goto badlib; } if (undef_tbl->num_entries == 0) break; } free(data); st_free_table(lib_tbl); } else { /* linear library, need to scan (FUTURE) */ for (;;) { int offset = SARMAG; int found = 0; struct exec hdr; struct nlist *syms, *sym, *end; while (undef_tbl->num_entries > 0) { found = 0; lseek(fd, offset, 0); size = read(fd, &ahdr, sizeof(ahdr)); if (size == -1) goto syserr; if (size == 0) break; if (size != sizeof(ahdr) || sscanf(ahdr.ar_size, "%d", &size) != 1) { goto badlib; } offset += sizeof(ahdr); if (load_header(fd, &hdr, offset) == -1) goto badlib; syms = load_sym(fd, &hdr, offset); if (syms == NULL) goto badlib; sym = syms; end = syms + (hdr.a_syms / sizeof(struct nlist)); while (sym < end) { if (sym->n_type == N_EXT|N_TEXT && st_lookup(undef_tbl, sym->n_un.n_name, NULL)) { break; } sym++; } if (sym < end) { found++; free(syms); if (load_1(fd, offset, 0) == -1) { goto badlib; } } offset += size; if (offset & 1) offset++; } if (found) break; } } close(fd); return 0; syserr: dln_errno = errno; badlib: if (fd >= 0) close(fd); return -1; } static int load(file) const char *file; { int fd; int result; if (dln_init_p == 0) { if (dln_init(dln_argv0) == -1) return -1; } result = strlen(file); if (file[result-1] == 'a') { return load_lib(file); } fd = open(file, O_RDONLY); if (fd == -1) { dln_errno = errno; return -1; } result = load_1(fd, 0, file); close(fd); return result; } void* dln_sym(name) const char *name; { struct nlist *sym; if (st_lookup(sym_tbl, name, &sym)) return (void*)sym->n_value; return NULL; } #endif /* USE_DLN_A_OUT */ #ifdef USE_DLN_DLOPEN # if defined(__NetBSD__) && defined(__NetBSD_Version__) && __NetBSD_Version__ < 105000000 # include # include # else # include # endif #endif #ifdef __hpux #include #include "dl.h" #endif #if defined(_AIX) #include /* for isdigit() */ #include /* for global errno */ #include #endif #ifdef NeXT #if NS_TARGET_MAJOR < 4 #include #else #include #ifndef NSLINKMODULE_OPTION_BINDNOW #define NSLINKMODULE_OPTION_BINDNOW 1 #endif #endif #else #ifdef __APPLE__ #include #endif #endif #if defined _WIN32 && !defined __CYGWIN__ #include #endif #ifdef _WIN32_WCE #undef FormatMessage #define FormatMessage FormatMessageA #undef LoadLibrary #define LoadLibrary LoadLibraryA #undef GetProcAddress #define GetProcAddress GetProcAddressA #endif static const char * dln_strerror() { #ifdef USE_DLN_A_OUT char *strerror(); switch (dln_errno) { case DLN_ECONFL: return "Symbol name conflict"; case DLN_ENOINIT: return "No initializer given"; case DLN_EUNDEF: return "Unresolved symbols"; case DLN_ENOTLIB: return "Not a library file"; case DLN_EBADLIB: return "Malformed library file"; case DLN_EINIT: return "Not initialized"; default: return strerror(dln_errno); } #endif #ifdef USE_DLN_DLOPEN return (char*)dlerror(); #endif #if defined _WIN32 && !defined __CYGWIN__ static char message[1024]; int error = GetLastError(); char *p = message; p += sprintf(message, "%d: ", error); FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), p, sizeof message - strlen(message), NULL); for (p = message; *p; p++) { if (*p == '\n' || *p == '\r') *p = ' '; } return message; #endif } #if defined(_AIX) && ! defined(_IA64) static void aix_loaderror(const char *pathname) { char *message[8], errbuf[1024]; int i,j; struct errtab { int errnum; char *errstr; } load_errtab[] = { {L_ERROR_TOOMANY, "too many errors, rest skipped."}, {L_ERROR_NOLIB, "can't load library:"}, {L_ERROR_UNDEF, "can't find symbol in library:"}, {L_ERROR_RLDBAD, "RLD index out of range or bad relocation type:"}, {L_ERROR_FORMAT, "not a valid, executable xcoff file:"}, {L_ERROR_MEMBER, "file not an archive or does not contain requested member:"}, {L_ERROR_TYPE, "symbol table mismatch:"}, {L_ERROR_ALIGN, "text alignment in file is wrong."}, {L_ERROR_SYSTEM, "System error:"}, {L_ERROR_ERRNO, NULL} }; #define LOAD_ERRTAB_LEN (sizeof(load_errtab)/sizeof(load_errtab[0])) #define ERRBUF_APPEND(s) strncat(errbuf, s, sizeof(errbuf)-strlen(errbuf)-1) snprintf(errbuf, 1024, "load failed - %s ", pathname); if (!loadquery(1, &message[0], sizeof(message))) ERRBUF_APPEND(strerror(errno)); for(i = 0; message[i] && *message[i]; i++) { int nerr = atoi(message[i]); for (j=0; j #include #include #include #include #include static char *vms_filespec; static int vms_fileact(char *filespec, int type); static long vms_fisexh(long *sigarr, long *mecarr); #endif #endif /* NO_DLN_LOAD */ void* dln_load(file) const char *file; { #ifdef NO_DLN_LOAD rb_raise(rb_eLoadError, "this executable file can't load extension libraries"); #else #if !defined(_AIX) && !defined(NeXT) const char *error = 0; #define DLN_ERROR() (error = dln_strerror(), strcpy(ALLOCA_N(char, strlen(error) + 1), error)) #endif #if defined _WIN32 && !defined __CYGWIN__ HINSTANCE handle; char winfile[MAXPATHLEN]; void (*init_fct)(); char *buf; if (strlen(file) >= MAXPATHLEN) rb_loaderror("filename too long"); /* Load the file as an object one */ init_funcname(&buf, file); strcpy(winfile, file); /* Load file */ if ((handle = LoadLibrary(winfile)) == NULL) { error = dln_strerror(); goto failed; } if ((init_fct = (void(*)())GetProcAddress(handle, buf)) == NULL) { rb_loaderror("%s - %s\n%s", dln_strerror(), buf, file); } /* Call the init code */ (*init_fct)(); return handle; #else #ifdef USE_DLN_A_OUT if (load(file) == -1) { error = dln_strerror(); goto failed; } return 0; #else char *buf; /* Load the file as an object one */ init_funcname(&buf, file); #ifdef USE_DLN_DLOPEN #define DLN_DEFINED { void *handle; void (*init_fct)(); #ifndef RTLD_LAZY # define RTLD_LAZY 1 #endif #ifdef __INTERIX # undef RTLD_GLOBAL #endif #ifndef RTLD_GLOBAL # define RTLD_GLOBAL 0 #endif /* Load file */ if ((handle = (void*)dlopen(file, RTLD_LAZY|RTLD_GLOBAL)) == NULL) { error = dln_strerror(); goto failed; } init_fct = (void(*)())dlsym(handle, buf); if (init_fct == NULL) { error = DLN_ERROR(); dlclose(handle); goto failed; } /* Call the init code */ (*init_fct)(); return handle; } #endif /* USE_DLN_DLOPEN */ #ifdef __hpux #define DLN_DEFINED { shl_t lib = NULL; int flags; void (*init_fct)(); flags = BIND_DEFERRED; lib = shl_load(file, flags, 0); if (lib == NULL) { extern int errno; rb_loaderror("%s - %s", strerror(errno), file); } shl_findsym(&lib, buf, TYPE_PROCEDURE, (void*)&init_fct); if (init_fct == NULL) { shl_findsym(&lib, buf, TYPE_UNDEFINED, (void*)&init_fct); if (init_fct == NULL) { errno = ENOSYM; rb_loaderror("%s - %s", strerror(ENOSYM), file); } } (*init_fct)(); return (void*)lib; } #endif /* hpux */ #if defined(_AIX) && ! defined(_IA64) #define DLN_DEFINED { void (*init_fct)(); init_fct = (void(*)())load((char*)file, 1, 0); if (init_fct == NULL) { aix_loaderror(file); } if (loadbind(0, (void*)dln_load, (void*)init_fct) == -1) { aix_loaderror(file); } (*init_fct)(); return (void*)init_fct; } #endif /* _AIX */ #if defined(NeXT) || defined(__APPLE__) #define DLN_DEFINED /*---------------------------------------------------- By SHIROYAMA Takayuki Psi@fortune.nest.or.jp Special Thanks... Yu tomoak-i@is.aist-nara.ac.jp, Mi hisho@tasihara.nest.or.jp, sunshine@sunshineco.com, and... Miss ARAI Akino(^^;) ----------------------------------------------------*/ #if defined(NeXT) && (NS_TARGET_MAJOR < 4)/* NeXTSTEP rld functions */ { NXStream* s; unsigned long init_address; char *object_files[2] = {NULL, NULL}; void (*init_fct)(); object_files[0] = (char*)file; s = NXOpenFile(2,NX_WRITEONLY); /* Load object file, if return value ==0 , load failed*/ if(rld_load(s, NULL, object_files, NULL) == 0) { NXFlush(s); NXClose(s); rb_loaderror("Failed to load %.200s", file); } /* lookup the initial function */ if(rld_lookup(s, buf, &init_address) == 0) { NXFlush(s); NXClose(s); rb_loaderror("Failed to lookup Init function %.200s", file); } NXFlush(s); NXClose(s); /* Cannot call *init_address directory, so copy this value to funtion pointer */ init_fct = (void(*)())init_address; (*init_fct)(); return (void*)init_address; } #else/* OPENSTEP dyld functions */ { int dyld_result; NSObjectFileImage obj_file; /* handle, but not use it */ /* "file" is module file name . "buf" is pointer to initial function name with "_" . */ void (*init_fct)(); dyld_result = NSCreateObjectFileImageFromFile(file, &obj_file); if (dyld_result != NSObjectFileImageSuccess) { rb_loaderror("Failed to load %.200s", file); } NSLinkModule(obj_file, file, NSLINKMODULE_OPTION_BINDNOW); /* lookup the initial function */ if(!NSIsSymbolNameDefined(buf)) { rb_loaderror("Failed to lookup Init function %.200s",file); } init_fct = NSAddressOfSymbol(NSLookupAndBindSymbol(buf)); (*init_fct)(); return (void*)init_fct; } #endif /* rld or dyld */ #endif #ifdef __BEOS__ # define DLN_DEFINED { status_t err_stat; /* BeOS error status code */ image_id img_id; /* extention module unique id */ void (*init_fct)(); /* initialize function for extention module */ /* load extention module */ img_id = load_add_on(file); if (img_id <= 0) { rb_loaderror("Failed to load %.200s", file); } /* find symbol for module initialize function. */ /* The Be Book KernelKit Images section described to use B_SYMBOL_TYPE_TEXT for symbol of function, not B_SYMBOL_TYPE_CODE. Why ? */ /* strcat(init_fct_symname, "__Fv"); */ /* parameter nothing. */ /* "__Fv" dont need! The Be Book Bug ? */ err_stat = get_image_symbol(img_id, buf, B_SYMBOL_TYPE_TEXT, (void **)&init_fct); if (err_stat != B_NO_ERROR) { char real_name[MAXPATHLEN]; strcpy(real_name, buf); strcat(real_name, "__Fv"); err_stat = get_image_symbol(img_id, real_name, B_SYMBOL_TYPE_TEXT, (void **)&init_fct); } if ((B_BAD_IMAGE_ID == err_stat) || (B_BAD_INDEX == err_stat)) { unload_add_on(img_id); rb_loaderror("Failed to lookup Init function %.200s", file); } else if (B_NO_ERROR != err_stat) { char errmsg[] = "Internal of BeOS version. %.200s (symbol_name = %s)"; unload_add_on(img_id); rb_loaderror(errmsg, strerror(err_stat), buf); } /* call module initialize function. */ (*init_fct)(); return (void*)img_id; } #endif /* __BEOS__*/ #ifdef __MACOS__ # define DLN_DEFINED { OSErr err; FSSpec libspec; CFragConnectionID connID; Ptr mainAddr; char errMessage[1024]; Boolean isfolder, didsomething; Str63 fragname; Ptr symAddr; CFragSymbolClass class; void (*init_fct)(); char fullpath[MAXPATHLEN]; strcpy(fullpath, file); /* resolve any aliases to find the real file */ c2pstr(fullpath); (void)FSMakeFSSpec(0, 0, fullpath, &libspec); err = ResolveAliasFile(&libspec, 1, &isfolder, &didsomething); if (err) { rb_loaderror("Unresolved Alias - %s", file); } /* Load the fragment (or return the connID if it is already loaded */ fragname[0] = 0; err = GetDiskFragment(&libspec, 0, 0, fragname, kLoadCFrag, &connID, &mainAddr, errMessage); if (err) { p2cstr(errMessage); rb_loaderror("%s - %s",errMessage , file); } /* Locate the address of the correct init function */ c2pstr(buf); err = FindSymbol(connID, buf, &symAddr, &class); if (err) { rb_loaderror("Unresolved symbols - %s" , file); } init_fct = (void (*)())symAddr; (*init_fct)(); return (void*)init_fct; } #endif /* __MACOS__ */ #if defined(__VMS) #define DLN_DEFINED { long status; void (*init_fct)(); char *fname, *p1, *p2; $DESCRIPTOR(fname_d, ""); $DESCRIPTOR(image_d, ""); $DESCRIPTOR(buf_d, ""); decc$to_vms(file, vms_fileact, 0, 0); fname = (char *)__alloca(strlen(file)+1); strcpy(fname,file); if (p1 = strrchr(fname,'/')) fname = p1 + 1; if (p2 = strrchr(fname,'.')) *p2 = '\0'; fname_d.dsc$w_length = strlen(fname); fname_d.dsc$a_pointer = fname; image_d.dsc$w_length = strlen(vms_filespec); image_d.dsc$a_pointer = vms_filespec; buf_d.dsc$w_length = strlen(buf); buf_d.dsc$a_pointer = buf; lib$establish(vms_fisexh); status = lib$find_image_symbol ( &fname_d, &buf_d, &init_fct, &image_d); lib$establish(0); if (status == RMS$_FNF) { error = dln_strerror(); goto failed; } else if (!$VMS_STATUS_SUCCESS(status)) { error = DLN_ERROR(); goto failed; } /* Call the init code */ (*init_fct)(); return 1; } #endif /* __VMS */ #ifndef DLN_DEFINED rb_notimplement(); #endif #endif /* USE_DLN_A_OUT */ #endif #if !defined(_AIX) && !defined(NeXT) failed: rb_loaderror("%s - %s", error, file); #endif #endif /* NO_DLN_LOAD */ return 0; /* dummy return */ } static char *dln_find_1(); char * dln_find_exe(fname, path) const char *fname; const char *path; { if (!path) { path = getenv(PATH_ENV); } if (!path) { #if defined(MSDOS) || defined(_WIN32) || defined(__human68k__) || defined(__MACOS__) path = "/usr/local/bin;/usr/ucb;/usr/bin;/bin;."; #else path = "/usr/local/bin:/usr/ucb:/usr/bin:/bin:."; #endif } return dln_find_1(fname, path, 1); } char * dln_find_file(fname, path) const char *fname; const char *path; { #ifndef __MACOS__ if (!path) path = "."; return dln_find_1(fname, path, 0); #else if (!path) path = "."; return _macruby_path_conv_posix_to_macos(dln_find_1(fname, path, 0)); #endif } static char fbuf[MAXPATHLEN]; static char * dln_find_1(fname, path, exe_flag) char *fname; char *path; int exe_flag; /* non 0 if looking for executable. */ { register char *dp; register char *ep; register char *bp; struct stat st; #ifdef __MACOS__ const char* mac_fullpath; #endif if (!fname) return fname; if (fname[0] == '/') return fname; if (strncmp("./", fname, 2) == 0 || strncmp("../", fname, 3) == 0) return fname; if (exe_flag && strchr(fname, '/')) return fname; #ifdef DOSISH if (fname[0] == '\\') return fname; # ifdef DOSISH_DRIVE_LETTER if (strlen(fname) > 2 && fname[1] == ':') return fname; # endif if (strncmp(".\\", fname, 2) == 0 || strncmp("..\\", fname, 3) == 0) return fname; if (exe_flag && strchr(fname, '\\')) return fname; #endif for (dp = path;; dp = ++ep) { register int l; int i; int fspace; /* extract a component */ ep = strchr(dp, PATH_SEP[0]); if (ep == NULL) ep = dp+strlen(dp); /* find the length of that component */ l = ep - dp; bp = fbuf; fspace = sizeof fbuf - 2; if (l > 0) { /* ** If the length of the component is zero length, ** start from the current directory. If the ** component begins with "~", start from the ** user's $HOME environment variable. Otherwise ** take the path literally. */ if (*dp == '~' && (l == 1 || #if defined(DOSISH) dp[1] == '\\' || #endif dp[1] == '/')) { char *home; home = getenv("HOME"); if (home != NULL) { i = strlen(home); if ((fspace -= i) < 0) goto toolong; memcpy(bp, home, i); bp += i; } dp++; l--; } if (l > 0) { if ((fspace -= l) < 0) goto toolong; memcpy(bp, dp, l); bp += l; } /* add a "/" between directory and filename */ if (ep[-1] != '/') *bp++ = '/'; } /* now append the file name */ i = strlen(fname); if ((fspace -= i) < 0) { toolong: fprintf(stderr, "openpath: pathname too long (ignored)\n"); *bp = '\0'; fprintf(stderr, "\tDirectory \"%s\"\n", fbuf); fprintf(stderr, "\tFile \"%s\"\n", fname); goto next; } memcpy(bp, fname, i + 1); #if defined(DOSISH) if (exe_flag) { static const char extension[][5] = { #if defined(MSDOS) ".com", ".exe", ".bat", #if defined(DJGPP) ".btm", ".sh", ".ksh", ".pl", ".sed", #endif #elif defined(__EMX__) || defined(_WIN32) ".exe", ".com", ".cmd", ".bat", /* end of __EMX__ or _WIN32 */ #else ".r", ".R", ".x", ".X", ".bat", ".BAT", /* __human68k__ */ #endif }; int j; for (j = 0; j < sizeof(extension) / sizeof(extension[0]); j++) { if (fspace < strlen(extension[j])) { fprintf(stderr, "openpath: pathname too long (ignored)\n"); fprintf(stderr, "\tDirectory \"%.*s\"\n", (int) (bp - fbuf), fbuf); fprintf(stderr, "\tFile \"%s%s\"\n", fname, extension[j]); continue; } strcpy(bp + i, extension[j]); #ifndef __MACOS__ if (stat(fbuf, &st) == 0) return fbuf; #else if (mac_fullpath = _macruby_exist_file_in_libdir_as_posix_name(fbuf)) return mac_fullpath; #endif } goto next; } #endif /* MSDOS or _WIN32 or __human68k__ or __EMX__ */ #ifndef __MACOS__ if (stat(fbuf, &st) == 0) { if (exe_flag == 0) return fbuf; /* looking for executable */ if (!S_ISDIR(st.st_mode) && eaccess(fbuf, X_OK) == 0) return fbuf; } #else if (mac_fullpath = _macruby_exist_file_in_libdir_as_posix_name(fbuf)) { if (exe_flag == 0) return mac_fullpath; /* looking for executable */ if (stat(mac_fullpath, &st) == 0) { if (!S_ISDIR(st.st_mode) && eaccess(mac_fullpath, X_OK) == 0) return mac_fullpath; } } #endif next: /* if not, and no other alternatives, life is bleak */ if (*ep == '\0') { return NULL; } /* otherwise try the next component in the search path */ } } #if defined(__VMS) /* action routine for decc$to_vms */ static int vms_fileact(char *filespec, int type) { if (vms_filespec) free(vms_filespec); vms_filespec = malloc(strlen(filespec)+1); strcpy(vms_filespec, filespec); return 1; } /* exception handler for LIB$FIND_IMAGE_SYMBOL */ static long vms_fisexh(long *sigarr, long *mecarr) { sys$unwind(1, 0); return 1; } #endif /* __VMS */ #define NO_DLN_LOAD 1 #include "dln.c" void Init_ext() { } /********************************************************************** enum.c - $Author: shyouhei $ $Date: 2007-02-13 00:01:19 +0100 (Tue, 13 Feb 2007) $ created at: Fri Oct 1 15:15:19 JST 1993 Copyright (C) 1993-2003 Yukihiro Matsumoto **********************************************************************/ #include "ruby.h" #include "node.h" #include "util.h" VALUE rb_mEnumerable; static ID id_each, id_eqq, id_cmp; VALUE rb_each(obj) VALUE obj; { return rb_funcall(obj, id_each, 0, 0); } static VALUE grep_i(i, arg) VALUE i, *arg; { if (RTEST(rb_funcall(arg[0], id_eqq, 1, i))) { rb_ary_push(arg[1], i); } return Qnil; } static VALUE grep_iter_i(i, arg) VALUE i, *arg; { if (RTEST(rb_funcall(arg[0], id_eqq, 1, i))) { rb_ary_push(arg[1], rb_yield(i)); } return Qnil; } /* * call-seq: * enum.grep(pattern) => array * enum.grep(pattern) {| obj | block } => array * * Returns an array of every element in enum for which * Pattern === element. If the optional block is * supplied, each matching element is passed to it, and the block's * result is stored in the output array. * * (1..100).grep 38..44 #=> [38, 39, 40, 41, 42, 43, 44] * c = IO.constants * c.grep(/SEEK/) #=> ["SEEK_END", "SEEK_SET", "SEEK_CUR"] * res = c.grep(/SEEK/) {|v| IO.const_get(v) } * res #=> [2, 0, 1] * */ static VALUE enum_grep(obj, pat) VALUE obj, pat; { VALUE ary = rb_ary_new(); VALUE arg[2]; arg[0] = pat; arg[1] = ary; rb_iterate(rb_each, obj, rb_block_given_p() ? grep_iter_i : grep_i, (VALUE)arg); return ary; } static VALUE find_i(i, memo) VALUE i; VALUE *memo; { if (RTEST(rb_yield(i))) { *memo = i; rb_iter_break(); } return Qnil; } /* * call-seq: * enum.detect(ifnone = nil) {| obj | block } => obj or nil * enum.find(ifnone = nil) {| obj | block } => obj or nil * * Passes each entry in enum to block. Returns the * first for which block is not false. If no * object matches, calls ifnone and returns its result when it * is specified, or returns nil * * (1..10).detect {|i| i % 5 == 0 and i % 7 == 0 } #=> nil * (1..100).detect {|i| i % 5 == 0 and i % 7 == 0 } #=> 35 * */ static VALUE enum_find(argc, argv, obj) int argc; VALUE* argv; VALUE obj; { VALUE memo = Qundef; VALUE if_none; rb_scan_args(argc, argv, "01", &if_none); rb_iterate(rb_each, obj, find_i, (VALUE)&memo); if (memo != Qundef) { return memo; } if (!NIL_P(if_none)) { return rb_funcall(if_none, rb_intern("call"), 0, 0); } return Qnil; } static VALUE find_all_i(i, ary) VALUE i, ary; { if (RTEST(rb_yield(i))) { rb_ary_push(ary, i); } return Qnil; } /* * call-seq: * enum.find_all {| obj | block } => array * enum.select {| obj | block } => array * * Returns an array containing all elements of enum for which * block is not false (see also * Enumerable#reject). * * (1..10).find_all {|i| i % 3 == 0 } #=> [3, 6, 9] * */ static VALUE enum_find_all(obj) VALUE obj; { VALUE ary = rb_ary_new(); rb_iterate(rb_each, obj, find_all_i, ary); return ary; } static VALUE reject_i(i, ary) VALUE i, ary; { if (!RTEST(rb_yield(i))) { rb_ary_push(ary, i); } return Qnil; } /* * call-seq: * enum.reject {| obj | block } => array * * Returns an array for all elements of enum for which * block is false (see also Enumerable#find_all). * * (1..10).reject {|i| i % 3 == 0 } #=> [1, 2, 4, 5, 7, 8, 10] * */ static VALUE enum_reject(obj) VALUE obj; { VALUE ary = rb_ary_new(); rb_iterate(rb_each, obj, reject_i, ary); return ary; } static VALUE collect_i(i, ary) VALUE i, ary; { rb_ary_push(ary, rb_yield(i)); return Qnil; } static VALUE collect_all(i, ary) VALUE i, ary; { rb_ary_push(ary, i); return Qnil; } /* * call-seq: * enum.collect {| obj | block } => array * enum.map {| obj | block } => array * * Returns a new array with the results of running block once * for every element in enum. * * (1..4).collect {|i| i*i } #=> [1, 4, 9, 16] * (1..4).collect { "cat" } #=> ["cat", "cat", "cat", "cat"] * */ static VALUE enum_collect(obj) VALUE obj; { VALUE ary = rb_ary_new(); rb_iterate(rb_each, obj, rb_block_given_p() ? collect_i : collect_all, ary); return ary; } /* * call-seq: * enum.to_a => array * enum.entries => array * * Returns an array containing the items in enum. * * (1..7).to_a #=> [1, 2, 3, 4, 5, 6, 7] * { 'a'=>1, 'b'=>2, 'c'=>3 }.to_a #=> [["a", 1], ["b", 2], ["c", 3]] */ static VALUE enum_to_a(obj) VALUE obj; { VALUE ary = rb_ary_new(); rb_iterate(rb_each, obj, collect_all, ary); return ary; } static VALUE inject_i(i, memo) VALUE i; VALUE *memo; { if (*memo == Qundef) { *memo = i; } else { *memo = rb_yield_values(2, *memo, i); } return Qnil; } /* * call-seq: * enum.inject(initial) {| memo, obj | block } => obj * enum.inject {| memo, obj | block } => obj * * Combines the elements of enum by applying the block to an * accumulator value (memo) and each element in turn. At each * step, memo is set to the value returned by the block. The * first form lets you supply an initial value for memo. The * second form uses the first element of the collection as a the * initial value (and skips that element while iterating). * * # Sum some numbers * (5..10).inject {|sum, n| sum + n } #=> 45 * # Multiply some numbers * (5..10).inject(1) {|product, n| product * n } #=> 151200 * * # find the longest word * longest = %w{ cat sheep bear }.inject do |memo,word| * memo.length > word.length ? memo : word * end * longest #=> "sheep" * * # find the length of the longest word * longest = %w{ cat sheep bear }.inject(0) do |memo,word| * memo >= word.length ? memo : word.length * end * longest #=> 5 * */ static VALUE enum_inject(argc, argv, obj) int argc; VALUE *argv, obj; { VALUE memo = Qundef; if (rb_scan_args(argc, argv, "01", &memo) == 0) memo = Qundef; rb_iterate(rb_each, obj, inject_i, (VALUE)&memo); if (memo == Qundef) return Qnil; return memo; } static VALUE partition_i(i, ary) VALUE i, *ary; { if (RTEST(rb_yield(i))) { rb_ary_push(ary[0], i); } else { rb_ary_push(ary[1], i); } return Qnil; } /* * call-seq: * enum.partition {| obj | block } => [ true_array, false_array ] * * Returns two arrays, the first containing the elements of * enum for which the block evaluates to true, the second * containing the rest. * * (1..6).partition {|i| (i&1).zero?} #=> [[2, 4, 6], [1, 3, 5]] * */ static VALUE enum_partition(obj) VALUE obj; { VALUE ary[2]; ary[0] = rb_ary_new(); ary[1] = rb_ary_new(); rb_iterate(rb_each, obj, partition_i, (VALUE)ary); return rb_assoc_new(ary[0], ary[1]); } /* * call-seq: * enum.sort => array * enum.sort {| a, b | block } => array * * Returns an array containing the items in enum sorted, * either according to their own <=> method, or by using * the results of the supplied block. The block should return -1, 0, or * +1 depending on the comparison between a and b. As of * Ruby 1.8, the method Enumerable#sort_by implements a * built-in Schwartzian Transform, useful when key computation or * comparison is expensive.. * * %w(rhea kea flea).sort #=> ["flea", "kea", "rhea"] * (1..10).sort {|a,b| b <=> a} #=> [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] */ static VALUE enum_sort(obj) VALUE obj; { return rb_ary_sort(enum_to_a(obj)); } static VALUE sort_by_i(i, ary) VALUE i, ary; { VALUE v; NODE *memo; v = rb_yield(i); if (RBASIC(ary)->klass) { rb_raise(rb_eRuntimeError, "sort_by reentered"); } memo = rb_node_newnode(NODE_MEMO, v, i, 0); rb_ary_push(ary, (VALUE)memo); return Qnil; } static int sort_by_cmp(aa, bb) NODE **aa, **bb; { VALUE a = aa[0]->u1.value; VALUE b = bb[0]->u1.value; return rb_cmpint(rb_funcall(a, id_cmp, 1, b), a, b); } /* * call-seq: * enum.sort_by {| obj | block } => array * * Sorts enum using a set of keys generated by mapping the * values in enum through the given block. * * %w{ apple pear fig }.sort_by {|word| word.length} #=> ["fig", "pear", "apple"] * * The current implementation of sort_by generates an * array of tuples containing the original collection element and the * mapped value. This makes sort_by fairly expensive when * the keysets are simple * * require 'benchmark' * include Benchmark * * a = (1..100000).map {rand(100000)} * * bm(10) do |b| * b.report("Sort") { a.sort } * b.report("Sort by") { a.sort_by {|a| a} } * end * * produces: * * user system total real * Sort 0.180000 0.000000 0.180000 ( 0.175469) * Sort by 1.980000 0.040000 2.020000 ( 2.013586) * * However, consider the case where comparing the keys is a non-trivial * operation. The following code sorts some files on modification time * using the basic sort method. * * files = Dir["*"] * sorted = files.sort {|a,b| File.new(a).mtime <=> File.new(b).mtime} * sorted #=> ["mon", "tues", "wed", "thurs"] * * This sort is inefficient: it generates two new File * objects during every comparison. A slightly better technique is to * use the Kernel#test method to generate the modification * times directly. * * files = Dir["*"] * sorted = files.sort { |a,b| * test(?M, a) <=> test(?M, b) * } * sorted #=> ["mon", "tues", "wed", "thurs"] * * This still generates many unnecessary Time objects. A * more efficient technique is to cache the sort keys (modification * times in this case) before the sort. Perl users often call this * approach a Schwartzian Transform, after Randal Schwartz. We * construct a temporary array, where each element is an array * containing our sort key along with the filename. We sort this array, * and then extract the filename from the result. * * sorted = Dir["*"].collect { |f| * [test(?M, f), f] * }.sort.collect { |f| f[1] } * sorted #=> ["mon", "tues", "wed", "thurs"] * * This is exactly what sort_by does internally. * * sorted = Dir["*"].sort_by {|f| test(?M, f)} * sorted #=> ["mon", "tues", "wed", "thurs"] */ static VALUE enum_sort_by(obj) VALUE obj; { VALUE ary; long i; if (TYPE(obj) == T_ARRAY) { ary = rb_ary_new2(RARRAY(obj)->len); } else { ary = rb_ary_new(); } RBASIC(ary)->klass = 0; rb_iterate(rb_each, obj, sort_by_i, ary); if (RARRAY(ary)->len > 1) { qsort(RARRAY(ary)->ptr, RARRAY(ary)->len, sizeof(VALUE), sort_by_cmp, 0); } if (RBASIC(ary)->klass) { rb_raise(rb_eRuntimeError, "sort_by reentered"); } for (i=0; ilen; i++) { RARRAY(ary)->ptr[i] = RNODE(RARRAY(ary)->ptr[i])->u2.value; } RBASIC(ary)->klass = rb_cArray; return ary; } static VALUE all_iter_i(i, memo) VALUE i; VALUE *memo; { if (!RTEST(rb_yield(i))) { *memo = Qfalse; rb_iter_break(); } return Qnil; } static VALUE all_i(i, memo) VALUE i; VALUE *memo; { if (!RTEST(i)) { *memo = Qfalse; rb_iter_break(); } return Qnil; } /* * call-seq: * enum.all? [{|obj| block } ] => true or false * * Passes each element of the collection to the given block. The method * returns true if the block never returns * false or nil. If the block is not given, * Ruby adds an implicit block of {|obj| obj} (that is * all? will return true only if none of the * collection members are false or nil.) * * %w{ ant bear cat}.all? {|word| word.length >= 3} #=> true * %w{ ant bear cat}.all? {|word| word.length >= 4} #=> false * [ nil, true, 99 ].all? #=> false * */ static VALUE enum_all(obj) VALUE obj; { VALUE result = Qtrue; rb_iterate(rb_each, obj, rb_block_given_p() ? all_iter_i : all_i, (VALUE)&result); return result; } static VALUE any_iter_i(i, memo) VALUE i; VALUE *memo; { if (RTEST(rb_yield(i))) { *memo = Qtrue; rb_iter_break(); } return Qnil; } static VALUE any_i(i, memo) VALUE i; VALUE *memo; { if (RTEST(i)) { *memo = Qtrue; rb_iter_break(); } return Qnil; } /* * call-seq: * enum.any? [{|obj| block } ] => true or false * * Passes each element of the collection to the given block. The method * returns true if the block ever returns a value other * than false or nil. If the block is not * given, Ruby adds an implicit block of {|obj| obj} (that * is any? will return true if at least one * of the collection members is not false or * nil. * * %w{ ant bear cat}.any? {|word| word.length >= 3} #=> true * %w{ ant bear cat}.any? {|word| word.length >= 4} #=> true * [ nil, true, 99 ].any? #=> true * */ static VALUE enum_any(obj) VALUE obj; { VALUE result = Qfalse; rb_iterate(rb_each, obj, rb_block_given_p() ? any_iter_i : any_i, (VALUE)&result); return result; } static VALUE min_i(i, memo) VALUE i; VALUE *memo; { VALUE cmp; if (*memo == Qundef) { *memo = i; } else { cmp = rb_funcall(i, id_cmp, 1, *memo); if (rb_cmpint(cmp, i, *memo) < 0) { *memo = i; } } return Qnil; } static VALUE min_ii(i, memo) VALUE i; VALUE *memo; { VALUE cmp; if (*memo == Qundef) { *memo = i; } else { cmp = rb_yield_values(2, i, *memo); if (rb_cmpint(cmp, i, *memo) < 0) { *memo = i; } } return Qnil; } /* * call-seq: * enum.min => obj * enum.min {| a,b | block } => obj * * Returns the object in enum with the minimum value. The * first form assumes all objects implement Comparable; * the second uses the block to return a <=> b. * * a = %w(albatross dog horse) * a.min #=> "albatross" * a.min {|a,b| a.length <=> b.length } #=> "dog" */ static VALUE enum_min(obj) VALUE obj; { VALUE result = Qundef; rb_iterate(rb_each, obj, rb_block_given_p() ? min_ii : min_i, (VALUE)&result); if (result == Qundef) return Qnil; return result; } /* * call-seq: * enum.max => obj * enum.max {| a,b | block } => obj * * Returns the object in enum with the maximum value. The * first form assumes all objects implement Comparable; * the second uses the block to return a <=> b. * * a = %w(albatross dog horse) * a.max #=> "horse" * a.max {|a,b| a.length <=> b.length } #=> "albatross" */ static VALUE max_i(i, memo) VALUE i; VALUE *memo; { VALUE cmp; if (*memo == Qundef) { *memo = i; } else { cmp = rb_funcall(i, id_cmp, 1, *memo); if (rb_cmpint(cmp, i, *memo) > 0) { *memo = i; } } return Qnil; } static VALUE max_ii(i, memo) VALUE i; VALUE *memo; { VALUE cmp; if (*memo == Qundef) { *memo = i; } else { cmp = rb_yield_values(2, i, *memo); if (rb_cmpint(cmp, i, *memo) > 0) { *memo = i; } } return Qnil; } /* * call-seq: * enum.max => obj * enum.max {|a,b| block } => obj * * Returns the object in _enum_ with the maximum value. The * first form assumes all objects implement Comparable; * the second uses the block to return a <=> b. * * a = %w(albatross dog horse) * a.max #=> "horse" * a.max {|a,b| a.length <=> b.length } #=> "albatross" */ static VALUE enum_max(obj) VALUE obj; { VALUE result = Qundef; rb_iterate(rb_each, obj, rb_block_given_p() ? max_ii : max_i, (VALUE)&result); if (result == Qundef) return Qnil; return result; } static VALUE member_i(item, memo) VALUE item; VALUE *memo; { if (rb_equal(item, memo[0])) { memo[1] = Qtrue; rb_iter_break(); } return Qnil; } /* * call-seq: * enum.include?(obj) => true or false * enum.member?(obj) => true or false * * Returns true if any member of enum equals * obj. Equality is tested using ==. * * IO.constants.include? "SEEK_SET" #=> true * IO.constants.include? "SEEK_NO_FURTHER" #=> false * */ static VALUE enum_member(obj, val) VALUE obj, val; { VALUE memo[2]; memo[0] = val; memo[1] = Qfalse; rb_iterate(rb_each, obj, member_i, (VALUE)memo); return memo[1]; } static VALUE each_with_index_i(val, memo) VALUE val; VALUE *memo; { rb_yield_values(2, val, INT2FIX(*memo)); ++*memo; return Qnil; } /* * call-seq: * enum.each_with_index {|obj, i| block } -> enum * * Calls block with two arguments, the item and its index, for * each item in enum. * * hash = Hash.new * %w(cat dog wombat).each_with_index {|item, index| * hash[item] = index * } * hash #=> {"cat"=>0, "wombat"=>2, "dog"=>1} * */ static VALUE enum_each_with_index(obj) VALUE obj; { VALUE memo = 0; rb_need_block(); rb_iterate(rb_each, obj, each_with_index_i, (VALUE)&memo); return obj; } static VALUE zip_i(val, memo) VALUE val; VALUE *memo; { VALUE result = memo[0]; VALUE args = memo[1]; int idx = memo[2]++; VALUE tmp; int i; tmp = rb_ary_new2(RARRAY(args)->len + 1); rb_ary_store(tmp, 0, val); for (i=0; ilen; i++) { rb_ary_push(tmp, rb_ary_entry(RARRAY(args)->ptr[i], idx)); } if (rb_block_given_p()) { rb_yield(tmp); } else { rb_ary_push(result, tmp); } return Qnil; } /* * call-seq: * enum.zip(arg, ...) => array * enum.zip(arg, ...) {|arr| block } => nil * * Converts any arguments to arrays, then merges elements of * enum with corresponding elements from each argument. This * generates a sequence of enum#size n-element * arrays, where n is one more that the count of arguments. If * the size of any argument is less than enum#size, * nil values are supplied. If a block given, it is * invoked for each output array, otherwise an array of arrays is * returned. * * a = [ 4, 5, 6 ] * b = [ 7, 8, 9 ] * * (1..3).zip(a, b) #=> [[1, 4, 7], [2, 5, 8], [3, 6, 9]] * "cat\ndog".zip([1]) #=> [["cat\n", 1], ["dog", nil]] * (1..3).zip #=> [[1], [2], [3]] * */ static VALUE enum_zip(argc, argv, obj) int argc; VALUE *argv; VALUE obj; { int i; VALUE result; VALUE memo[3]; for (i=0; iEnumerable mixin provides collection classes with * several traversal and searching methods, and with the ability to * sort. The class must provide a method each, which * yields successive members of the collection. If * Enumerable#max, #min, or * #sort is used, the objects in the collection must also * implement a meaningful <=> operator, as these methods * rely on an ordering between members of the collection. */ void Init_Enumerable() { rb_mEnumerable = rb_define_module("Enumerable"); rb_define_method(rb_mEnumerable,"to_a", enum_to_a, 0); rb_define_method(rb_mEnumerable,"entries", enum_to_a, 0); rb_define_method(rb_mEnumerable,"sort", enum_sort, 0); rb_define_method(rb_mEnumerable,"sort_by", enum_sort_by, 0); rb_define_method(rb_mEnumerable,"grep", enum_grep, 1); rb_define_method(rb_mEnumerable,"find", enum_find, -1); rb_define_method(rb_mEnumerable,"detect", enum_find, -1); rb_define_method(rb_mEnumerable,"find_all", enum_find_all, 0); rb_define_method(rb_mEnumerable,"select", enum_find_all, 0); rb_define_method(rb_mEnumerable,"reject", enum_reject, 0); rb_define_method(rb_mEnumerable,"collect", enum_collect, 0); rb_define_method(rb_mEnumerable,"map", enum_collect, 0); rb_define_method(rb_mEnumerable,"inject", enum_inject, -1); rb_define_method(rb_mEnumerable,"partition", enum_partition, 0); rb_define_method(rb_mEnumerable,"all?", enum_all, 0); rb_define_method(rb_mEnumerable,"any?", enum_any, 0); rb_define_method(rb_mEnumerable,"min", enum_min, 0); rb_define_method(rb_mEnumerable,"max", enum_max, 0); rb_define_method(rb_mEnumerable,"member?", enum_member, 1); rb_define_method(rb_mEnumerable,"include?", enum_member, 1); rb_define_method(rb_mEnumerable,"each_with_index", enum_each_with_index, 0); rb_define_method(rb_mEnumerable, "zip", enum_zip, -1); id_eqq = rb_intern("==="); id_each = rb_intern("each"); id_cmp = rb_intern("<=>"); } /********************************************************************** error.c - $Author: shyouhei $ $Date: 2008-08-04 05:16:55 +0200 (Mon, 04 Aug 2008) $ created at: Mon Aug 9 16:11:34 JST 1993 Copyright (C) 1993-2003 Yukihiro Matsumoto **********************************************************************/ #include "ruby.h" #include "env.h" #include "st.h" #include #ifdef HAVE_STDARG_PROTOTYPES #include #define va_init_list(a,b) va_start(a,b) #else #include #define va_init_list(a,b) va_start(a) #endif #ifdef HAVE_STDLIB_H #include #endif #ifndef EXIT_SUCCESS #define EXIT_SUCCESS 0 #endif extern const char ruby_version[], ruby_release_date[], ruby_platform[]; int ruby_nerrs; static int err_position(buf, len) char *buf; long len; { ruby_set_current_source(); if (!ruby_sourcefile) { return 0; } else if (ruby_sourceline == 0) { return snprintf(buf, len, "%s: ", ruby_sourcefile); } else { return snprintf(buf, len, "%s:%d: ", ruby_sourcefile, ruby_sourceline); } } static void err_snprintf(buf, len, fmt, args) char *buf; long len; const char *fmt; va_list args; { long n; n = err_position(buf, len); if (len > n) { vsnprintf((char*)buf+n, len-n, fmt, args); } } static void err_append _((const char*)); static void err_print(fmt, args) const char *fmt; va_list args; { char buf[BUFSIZ]; err_snprintf(buf, BUFSIZ, fmt, args); err_append(buf); } void #ifdef HAVE_STDARG_PROTOTYPES rb_compile_error(const char *fmt, ...) #else rb_compile_error(fmt, va_alist) const char *fmt; va_dcl #endif { va_list args; va_init_list(args, fmt); err_print(fmt, args); va_end(args); ruby_nerrs++; } void #ifdef HAVE_STDARG_PROTOTYPES rb_compile_error_append(const char *fmt, ...) #else rb_compile_error_append(fmt, va_alist) const char *fmt; va_dcl #endif { va_list args; char buf[BUFSIZ]; va_init_list(args, fmt); vsnprintf(buf, BUFSIZ, fmt, args); va_end(args); err_append(buf); } static void warn_print(fmt, args) const char *fmt; va_list args; { char buf[BUFSIZ]; int len; err_snprintf(buf, BUFSIZ, fmt, args); len = strlen(buf); buf[len++] = '\n'; rb_write_error2(buf, len); } void #ifdef HAVE_STDARG_PROTOTYPES rb_warn(const char *fmt, ...) #else rb_warn(fmt, va_alist) const char *fmt; va_dcl #endif { char buf[BUFSIZ]; va_list args; if (NIL_P(ruby_verbose)) return; snprintf(buf, BUFSIZ, "warning: %s", fmt); va_init_list(args, fmt); warn_print(buf, args); va_end(args); } /* rb_warning() reports only in verbose mode */ void #ifdef HAVE_STDARG_PROTOTYPES rb_warning(const char *fmt, ...) #else rb_warning(fmt, va_alist) const char *fmt; va_dcl #endif { char buf[BUFSIZ]; va_list args; if (!RTEST(ruby_verbose)) return; snprintf(buf, BUFSIZ, "warning: %s", fmt); va_init_list(args, fmt); warn_print(buf, args); va_end(args); } /* * call-seq: * warn(msg) => nil * * Display the given message (followed by a newline) on STDERR unless * warnings are disabled (for example with the -W0 flag). */ static VALUE rb_warn_m(self, mesg) VALUE self, mesg; { if (!NIL_P(ruby_verbose)) { rb_io_write(rb_stderr, mesg); rb_io_write(rb_stderr, rb_default_rs); } return Qnil; } void #ifdef HAVE_STDARG_PROTOTYPES rb_bug(const char *fmt, ...) #else rb_bug(fmt, va_alist) const char *fmt; va_dcl #endif { char buf[BUFSIZ]; va_list args; FILE *out = stderr; int len = err_position(buf, BUFSIZ); if (fwrite(buf, 1, len, out) == len || fwrite(buf, 1, len, (out = stdout)) == len) { fputs("[BUG] ", out); va_init_list(args, fmt); vfprintf(out, fmt, args); va_end(args); fprintf(out, "\nruby %s (%s) [%s]\n\n", ruby_version, ruby_release_date, ruby_platform); } abort(); } static struct types { int type; const char *name; } builtin_types[] = { {T_NIL, "nil"}, {T_OBJECT, "Object"}, {T_CLASS, "Class"}, {T_ICLASS, "iClass"}, /* internal use: mixed-in module holder */ {T_MODULE, "Module"}, {T_FLOAT, "Float"}, {T_STRING, "String"}, {T_REGEXP, "Regexp"}, {T_ARRAY, "Array"}, {T_FIXNUM, "Fixnum"}, {T_HASH, "Hash"}, {T_STRUCT, "Struct"}, {T_BIGNUM, "Bignum"}, {T_FILE, "File"}, {T_TRUE, "true"}, {T_FALSE, "false"}, {T_SYMBOL, "Symbol"}, /* :symbol */ {T_DATA, "Data"}, /* internal use: wrapped C pointers */ {T_MATCH, "MatchData"}, /* data of $~ */ {T_VARMAP, "Varmap"}, /* internal use: dynamic variables */ {T_SCOPE, "Scope"}, /* internal use: variable scope */ {T_NODE, "Node"}, /* internal use: syntax tree node */ {T_UNDEF, "undef"}, /* internal use: #undef; should not happen */ {-1, 0} }; void rb_check_type(x, t) VALUE x; int t; { struct types *type = builtin_types; if (x == Qundef) { rb_bug("undef leaked to the Ruby space"); } if (TYPE(x) != t) { while (type->type >= 0) { if (type->type == t) { char *etype; if (NIL_P(x)) { etype = "nil"; } else if (FIXNUM_P(x)) { etype = "Fixnum"; } else if (SYMBOL_P(x)) { etype = "Symbol"; } else if (rb_special_const_p(x)) { etype = RSTRING(rb_obj_as_string(x))->ptr; } else { etype = rb_obj_classname(x); } rb_raise(rb_eTypeError, "wrong argument type %s (expected %s)", etype, type->name); } type++; } rb_bug("unknown type 0x%x", t); } } /* exception classes */ #include VALUE rb_eException; VALUE rb_eSystemExit; VALUE rb_eInterrupt; VALUE rb_eSignal; VALUE rb_eFatal; VALUE rb_eStandardError; VALUE rb_eRuntimeError; VALUE rb_eTypeError; VALUE rb_eArgError; VALUE rb_eIndexError; VALUE rb_eRangeError; VALUE rb_eNameError; VALUE rb_eNoMethodError; VALUE rb_eSecurityError; VALUE rb_eNotImpError; VALUE rb_eNoMemError; VALUE rb_cNameErrorMesg; VALUE rb_eScriptError; VALUE rb_eSyntaxError; VALUE rb_eLoadError; VALUE rb_eSystemCallError; VALUE rb_mErrno; VALUE rb_exc_new(etype, ptr, len) VALUE etype; const char *ptr; long len; { return rb_funcall(etype, rb_intern("new"), 1, rb_str_new(ptr, len)); } VALUE rb_exc_new2(etype, s) VALUE etype; const char *s; { return rb_exc_new(etype, s, strlen(s)); } VALUE rb_exc_new3(etype, str) VALUE etype, str; { StringValue(str); return rb_funcall(etype, rb_intern("new"), 1, str); } /* * call-seq: * Exception.new(msg = nil) => exception * * Construct a new Exception object, optionally passing in * a message. */ static VALUE exc_initialize(argc, argv, exc) int argc; VALUE *argv; VALUE exc; { VALUE arg; rb_scan_args(argc, argv, "01", &arg); rb_iv_set(exc, "mesg", arg); rb_iv_set(exc, "bt", Qnil); return exc; } /* * Document-method: exception * * call-seq: * exc.exception(string) -> an_exception or exc * * With no argument, or if the argument is the same as the receiver, * return the receiver. Otherwise, create a new * exception object of the same class as the receiver, but with a * message equal to string.to_str. * */ static VALUE exc_exception(argc, argv, self) int argc; VALUE *argv; VALUE self; { VALUE exc; if (argc == 0) return self; if (argc == 1 && self == argv[0]) return self; exc = rb_obj_clone(self); exc_initialize(argc, argv, exc); return exc; } /* * call-seq: * exception.to_s => string * * Returns exception's message (or the name of the exception if * no message is set). */ static VALUE exc_to_s(exc) VALUE exc; { VALUE mesg = rb_attr_get(exc, rb_intern("mesg")); if (NIL_P(mesg)) return rb_class_name(CLASS_OF(exc)); if (OBJ_TAINTED(exc)) OBJ_TAINT(mesg); return mesg; } /* * call-seq: * exception.message => string * exception.to_str => string * * Returns the result of invoking exception.to_s. * Normally this returns the exception's message or name. By * supplying a to_str method, exceptions are agreeing to * be used where Strings are expected. */ static VALUE exc_to_str(exc) VALUE exc; { return rb_funcall(exc, rb_intern("to_s"), 0, 0); } /* * call-seq: * exception.inspect => string * * Return this exception's class name an message */ static VALUE exc_inspect(exc) VALUE exc; { VALUE str, klass; klass = CLASS_OF(exc); exc = rb_obj_as_string(exc); if (RSTRING(exc)->len == 0) { return rb_str_dup(rb_class_name(klass)); } str = rb_str_buf_new2("#<"); klass = rb_class_name(klass); rb_str_buf_append(str, klass); rb_str_buf_cat(str, ": ", 2); rb_str_buf_append(str, exc); rb_str_buf_cat(str, ">", 1); return str; } /* * call-seq: * exception.backtrace => array * * Returns any backtrace associated with the exception. The backtrace * is an array of strings, each containing either ``filename:lineNo: in * `method''' or ``filename:lineNo.'' * * def a * raise "boom" * end * * def b * a() * end * * begin * b() * rescue => detail * print detail.backtrace.join("\n") * end * * produces: * * prog.rb:2:in `a' * prog.rb:6:in `b' * prog.rb:10 */ static VALUE exc_backtrace(exc) VALUE exc; { static ID bt; if (!bt) bt = rb_intern("bt"); return rb_attr_get(exc, bt); } VALUE rb_check_backtrace(bt) VALUE bt; { long i; static char *err = "backtrace must be Array of String"; if (!NIL_P(bt)) { int t = TYPE(bt); if (t == T_STRING) return rb_ary_new3(1, bt); if (t != T_ARRAY) { rb_raise(rb_eTypeError, err); } for (i=0;ilen;i++) { if (TYPE(RARRAY(bt)->ptr[i]) != T_STRING) { rb_raise(rb_eTypeError, err); } } } return bt; } /* * call-seq: * exc.set_backtrace(array) => array * * Sets the backtrace information associated with exc. The * argument must be an array of String objects in the * format described in Exception#backtrace. * */ static VALUE exc_set_backtrace(exc, bt) VALUE exc; VALUE bt; { return rb_iv_set(exc, "bt", rb_check_backtrace(bt)); } /* * call-seq: * SystemExit.new(status=0) => system_exit * * Create a new +SystemExit+ exception with the given status. */ static VALUE exit_initialize(argc, argv, exc) int argc; VALUE *argv; VALUE exc; { VALUE status = INT2FIX(EXIT_SUCCESS); if (argc > 0 && FIXNUM_P(argv[0])) { status = *argv++; --argc; } rb_call_super(argc, argv); rb_iv_set(exc, "status", status); return exc; } /* * call-seq: * system_exit.status => fixnum * * Return the status value associated with this system exit. */ static VALUE exit_status(exc) VALUE exc; { return rb_attr_get(exc, rb_intern("status")); } /* * call-seq: * system_exit.success? => true or false * * Returns +true+ if exiting successful, +false+ if not. */ static VALUE exit_success_p(exc) VALUE exc; { VALUE status = rb_attr_get(exc, rb_intern("status")); if (NIL_P(status)) return Qtrue; if (status == INT2FIX(EXIT_SUCCESS)) return Qtrue; return Qfalse; } void #ifdef HAVE_STDARG_PROTOTYPES rb_name_error(ID id, const char *fmt, ...) #else rb_name_error(id, fmt, va_alist) ID id; const char *fmt; va_dcl #endif { VALUE exc, argv[2]; va_list args; char buf[BUFSIZ]; va_init_list(args, fmt); vsnprintf(buf, BUFSIZ, fmt, args); va_end(args); argv[0] = rb_str_new2(buf); argv[1] = ID2SYM(id); exc = rb_class_new_instance(2, argv, rb_eNameError); rb_exc_raise(exc); } /* * call-seq: * NameError.new(msg [, name]) => name_error * * Construct a new NameError exception. If given the name * parameter may subsequently be examined using the NameError.name * method. */ static VALUE name_err_initialize(argc, argv, self) int argc; VALUE *argv; VALUE self; { VALUE name; name = (argc > 1) ? argv[--argc] : Qnil; rb_call_super(argc, argv); rb_iv_set(self, "name", name); return self; } /* * call-seq: * name_error.name => string or nil * * Return the name associated with this NameError exception. */ static VALUE name_err_name(self) VALUE self; { return rb_attr_get(self, rb_intern("name")); } /* * call-seq: * name_error.to_s => string * * Produce a nicely-formated string representing the +NameError+. */ static VALUE name_err_to_s(exc) VALUE exc; { VALUE mesg = rb_attr_get(exc, rb_intern("mesg")), str = mesg; if (NIL_P(mesg)) return rb_class_name(CLASS_OF(exc)); StringValue(str); if (str != mesg) { rb_iv_set(exc, "mesg", mesg = str); } if (OBJ_TAINTED(exc)) OBJ_TAINT(mesg); return mesg; } /* * call-seq: * NoMethodError.new(msg, name [, args]) => no_method_error * * Construct a NoMethodError exception for a method of the given name * called with the given arguments. The name may be accessed using * the #name method on the resulting object, and the * arguments using the #args method. */ static VALUE nometh_err_initialize(argc, argv, self) int argc; VALUE *argv; VALUE self; { VALUE args = (argc > 2) ? argv[--argc] : Qnil; name_err_initialize(argc, argv, self); rb_iv_set(self, "args", args); return self; } /* :nodoc: */ static void name_err_mesg_mark(ptr) VALUE *ptr; { rb_gc_mark_locations(ptr, ptr+3); } /* :nodoc: */ static VALUE name_err_mesg_new(obj, mesg, recv, method) VALUE obj, mesg, recv, method; { VALUE *ptr = ALLOC_N(VALUE, 3); ptr[0] = mesg; ptr[1] = recv; ptr[2] = method; return Data_Wrap_Struct(rb_cNameErrorMesg, name_err_mesg_mark, -1, ptr); } /* :nodoc: */ static VALUE name_err_mesg_to_str(obj) VALUE obj; { VALUE *ptr, mesg; Data_Get_Struct(obj, VALUE, ptr); mesg = ptr[0]; if (NIL_P(mesg)) return Qnil; else { char *desc = 0; VALUE d = 0, args[3]; obj = ptr[1]; switch (TYPE(obj)) { case T_NIL: desc = "nil"; break; case T_TRUE: desc = "true"; break; case T_FALSE: desc = "false"; break; default: d = rb_protect(rb_inspect, obj, 0); if (NIL_P(d) || RSTRING(d)->len > 65) { d = rb_any_to_s(obj); } desc = RSTRING(d)->ptr; break; } if (desc && desc[0] != '#') { d = rb_str_new2(desc); rb_str_cat2(d, ":"); rb_str_cat2(d, rb_obj_classname(obj)); } args[0] = mesg; args[1] = ptr[2]; args[2] = d; mesg = rb_f_sprintf(3, args); } if (OBJ_TAINTED(obj)) OBJ_TAINT(mesg); return mesg; } /* :nodoc: */ static VALUE name_err_mesg_load(klass, str) VALUE klass, str; { return str; } /* * call-seq: * no_method_error.args => obj * * Return the arguments passed in as the third parameter to * the constructor. */ static VALUE nometh_err_args(self) VALUE self; { return rb_attr_get(self, rb_intern("args")); } void rb_invalid_str(str, type) const char *str, *type; { VALUE s = rb_str_inspect(rb_str_new2(str)); rb_raise(rb_eArgError, "invalid value for %s: %s", type, RSTRING(s)->ptr); } /* * Document-module: Errno * * Ruby exception objects are subclasses of Exception. * However, operating systems typically report errors using plain * integers. Module Errno is created dynamically to map * these operating system errors to Ruby classes, with each error * number generating its own subclass of SystemCallError. * As the subclass is created in module Errno, its name * will start Errno::. * * The names of the Errno:: classes depend on * the environment in which Ruby runs. On a typical Unix or Windows * platform, there are Errno classes such as * Errno::EACCES, Errno::EAGAIN, * Errno::EINTR, and so on. * * The integer operating system error number corresponding to a * particular error is available as the class constant * Errno::error::Errno. * * Errno::EACCES::Errno #=> 13 * Errno::EAGAIN::Errno #=> 11 * Errno::EINTR::Errno #=> 4 * * The full list of operating system errors on your particular platform * are available as the constants of Errno. * * Errno.constants #=> E2BIG, EACCES, EADDRINUSE, EADDRNOTAVAIL, ... */ static st_table *syserr_tbl; static VALUE set_syserr(n, name) int n; const char *name; { VALUE error; if (!st_lookup(syserr_tbl, n, &error)) { error = rb_define_class_under(rb_mErrno, name, rb_eSystemCallError); rb_define_const(error, "Errno", INT2NUM(n)); st_add_direct(syserr_tbl, n, error); } else { rb_define_const(rb_mErrno, name, error); } return error; } static VALUE get_syserr(n) int n; { VALUE error; if (!st_lookup(syserr_tbl, n, &error)) { char name[8]; /* some Windows' errno have 5 digits. */ snprintf(name, sizeof(name), "E%03d", n); error = set_syserr(n, name); } return error; } /* * call-seq: * SystemCallError.new(msg, errno) => system_call_error_subclass * * If _errno_ corresponds to a known system error code, constructs * the appropriate Errno class for that error, otherwise * constructs a generic SystemCallError object. The * error number is subsequently available via the errno * method. */ static VALUE syserr_initialize(argc, argv, self) int argc; VALUE *argv; VALUE self; { #if !defined(_WIN32) && !defined(__VMS) char *strerror(); #endif char *err; VALUE mesg, error; VALUE klass = rb_obj_class(self); if (klass == rb_eSystemCallError) { rb_scan_args(argc, argv, "11", &mesg, &error); if (argc == 1 && FIXNUM_P(mesg)) { error = mesg; mesg = Qnil; } if (!NIL_P(error) && st_lookup(syserr_tbl, NUM2LONG(error), &klass)) { /* change class */ if (TYPE(self) != T_OBJECT) { /* insurance to avoid type crash */ rb_raise(rb_eTypeError, "invalid instance type"); } RBASIC(self)->klass = klass; } } else { rb_scan_args(argc, argv, "01", &mesg); error = rb_const_get(klass, rb_intern("Errno")); } if (!NIL_P(error)) err = strerror(NUM2LONG(error)); else err = "unknown error"; if (!NIL_P(mesg)) { VALUE str = mesg; size_t len; StringValue(str); len = strlen(err)+RSTRING(str)->len+3; mesg = rb_str_new(0, len); snprintf(RSTRING(mesg)->ptr, len+1, "%s - %.*s", err, (int)RSTRING(str)->len, RSTRING(str)->ptr); rb_str_resize(mesg, strlen(RSTRING(mesg)->ptr)); } else { mesg = rb_str_new2(err); } rb_call_super(1, &mesg); rb_iv_set(self, "errno", error); return self; } /* * call-seq: * system_call_error.errno => fixnum * * Return this SystemCallError's error number. */ static VALUE syserr_errno(self) VALUE self; { return rb_attr_get(self, rb_intern("errno")); } /* * call-seq: * system_call_error === other => true or false * * Return +true+ if the receiver is a generic +SystemCallError+, or * if the error numbers _self_ and _other_ are the same. */ static VALUE syserr_eqq(self, exc) VALUE self, exc; { VALUE num, e; if (!rb_obj_is_kind_of(exc, rb_eSystemCallError)) return Qfalse; if (self == rb_eSystemCallError) return Qtrue; num = rb_attr_get(exc, rb_intern("errno")); if (NIL_P(num)) { VALUE klass = CLASS_OF(exc); while (TYPE(klass) == T_ICLASS || FL_TEST(klass, FL_SINGLETON)) { klass = (VALUE)RCLASS(klass)->super; } num = rb_const_get(klass, rb_intern("Errno")); } e = rb_const_get(self, rb_intern("Errno")); if (FIXNUM_P(num) ? num == e : rb_equal(num, e)) return Qtrue; return Qfalse; } /* * Descendents of class Exception are used to communicate * between raise methods and rescue * statements in begin/end blocks. Exception * objects carry information about the exception---its type (the * exception's class name), an optional descriptive string, and * optional traceback information. Programs may subclass * Exception to add additional information. */ void Init_Exception() { rb_eException = rb_define_class("Exception", rb_cObject); rb_define_singleton_method(rb_eException, "exception", rb_class_new_instance, -1); rb_define_method(rb_eException, "exception", exc_exception, -1); rb_define_method(rb_eException, "initialize", exc_initialize, -1); rb_define_method(rb_eException, "to_s", exc_to_s, 0); rb_define_method(rb_eException, "to_str", exc_to_str, 0); rb_define_method(rb_eException, "message", exc_to_str, 0); rb_define_method(rb_eException, "inspect", exc_inspect, 0); rb_define_method(rb_eException, "backtrace", exc_backtrace, 0); rb_define_method(rb_eException, "set_backtrace", exc_set_backtrace, 1); rb_eSystemExit = rb_define_class("SystemExit", rb_eException); rb_define_method(rb_eSystemExit, "initialize", exit_initialize, -1); rb_define_method(rb_eSystemExit, "status", exit_status, 0); rb_define_method(rb_eSystemExit, "success?", exit_success_p, 0); rb_eFatal = rb_define_class("fatal", rb_eException); rb_eSignal = rb_define_class("SignalException", rb_eException); rb_eInterrupt = rb_define_class("Interrupt", rb_eSignal); rb_eStandardError = rb_define_class("StandardError", rb_eException); rb_eTypeError = rb_define_class("TypeError", rb_eStandardError); rb_eArgError = rb_define_class("ArgumentError", rb_eStandardError); rb_eIndexError = rb_define_class("IndexError", rb_eStandardError); rb_eRangeError = rb_define_class("RangeError", rb_eStandardError); rb_eNameError = rb_define_class("NameError", rb_eStandardError); rb_define_method(rb_eNameError, "initialize", name_err_initialize, -1); rb_define_method(rb_eNameError, "name", name_err_name, 0); rb_define_method(rb_eNameError, "to_s", name_err_to_s, 0); rb_cNameErrorMesg = rb_define_class_under(rb_eNameError, "message", rb_cData); rb_define_singleton_method(rb_cNameErrorMesg, "!", name_err_mesg_new, 3); rb_define_method(rb_cNameErrorMesg, "to_str", name_err_mesg_to_str, 0); rb_define_method(rb_cNameErrorMesg, "_dump", name_err_mesg_to_str, 1); rb_define_singleton_method(rb_cNameErrorMesg, "_load", name_err_mesg_load, 1); rb_eNoMethodError = rb_define_class("NoMethodError", rb_eNameError); rb_define_method(rb_eNoMethodError, "initialize", nometh_err_initialize, -1); rb_define_method(rb_eNoMethodError, "args", nometh_err_args, 0); rb_eScriptError = rb_define_class("ScriptError", rb_eException); rb_eSyntaxError = rb_define_class("SyntaxError", rb_eScriptError); rb_eLoadError = rb_define_class("LoadError", rb_eScriptError); rb_eNotImpError = rb_define_class("NotImplementedError", rb_eScriptError); rb_eRuntimeError = rb_define_class("RuntimeError", rb_eStandardError); rb_eSecurityError = rb_define_class("SecurityError", rb_eStandardError); rb_eNoMemError = rb_define_class("NoMemoryError", rb_eException); syserr_tbl = st_init_numtable(); rb_eSystemCallError = rb_define_class("SystemCallError", rb_eStandardError); rb_define_method(rb_eSystemCallError, "initialize", syserr_initialize, -1); rb_define_method(rb_eSystemCallError, "errno", syserr_errno, 0); rb_define_singleton_method(rb_eSystemCallError, "===", syserr_eqq, 1); rb_mErrno = rb_define_module("Errno"); rb_define_global_function("warn", rb_warn_m, 1); } void #ifdef HAVE_STDARG_PROTOTYPES rb_raise(VALUE exc, const char *fmt, ...) #else rb_raise(exc, fmt, va_alist) VALUE exc; const char *fmt; va_dcl #endif { va_list args; char buf[BUFSIZ]; va_init_list(args,fmt); vsnprintf(buf, BUFSIZ, fmt, args); va_end(args); rb_exc_raise(rb_exc_new2(exc, buf)); } void #ifdef HAVE_STDARG_PROTOTYPES rb_loaderror(const char *fmt, ...) #else rb_loaderror(fmt, va_alist) const char *fmt; va_dcl #endif { va_list args; char buf[BUFSIZ]; va_init_list(args, fmt); vsnprintf(buf, BUFSIZ, fmt, args); va_end(args); rb_exc_raise(rb_exc_new2(rb_eLoadError, buf)); } void rb_notimplement() { rb_raise(rb_eNotImpError, "%s() function is unimplemented on this machine", rb_id2name(ruby_frame->last_func)); } void #ifdef HAVE_STDARG_PROTOTYPES rb_fatal(const char *fmt, ...) #else rb_fatal(fmt, va_alist) const char *fmt; va_dcl #endif { va_list args; char buf[BUFSIZ]; va_init_list(args, fmt); vsnprintf(buf, BUFSIZ, fmt, args); va_end(args); ruby_in_eval = 0; rb_exc_fatal(rb_exc_new2(rb_eFatal, buf)); } void rb_sys_fail(mesg) const char *mesg; { int n = errno; VALUE arg; errno = 0; if (n == 0) { rb_bug("rb_sys_fail(%s) - errno == 0", mesg ? mesg : ""); } arg = mesg ? rb_str_new2(mesg) : Qnil; rb_exc_raise(rb_class_new_instance(1, &arg, get_syserr(n))); } void #ifdef HAVE_STDARG_PROTOTYPES rb_sys_warning(const char *fmt, ...) #else rb_sys_warning(fmt, va_alist) const char *fmt; va_dcl #endif { char buf[BUFSIZ]; va_list args; int errno_save; errno_save = errno; if (!RTEST(ruby_verbose)) return; snprintf(buf, BUFSIZ, "warning: %s", fmt); snprintf(buf+strlen(buf), BUFSIZ-strlen(buf), ": %s", strerror(errno_save)); va_init_list(args, fmt); warn_print(buf, args); va_end(args); errno = errno_save; } void rb_load_fail(path) const char *path; { rb_loaderror("%s -- %s", strerror(errno), path); } void rb_error_frozen(what) const char *what; { rb_raise(rb_eTypeError, "can't modify frozen %s", what); } void rb_check_frozen(obj) VALUE obj; { if (OBJ_FROZEN(obj)) rb_error_frozen(rb_obj_classname(obj)); } void Init_syserr() { #ifdef EPERM set_syserr(EPERM, "EPERM"); #endif #ifdef ENOENT set_syserr(ENOENT, "ENOENT"); #endif #ifdef ESRCH set_syserr(ESRCH, "ESRCH"); #endif #ifdef EINTR set_syserr(EINTR, "EINTR"); #endif #ifdef EIO set_syserr(EIO, "EIO"); #endif #ifdef ENXIO set_syserr(ENXIO, "ENXIO"); #endif #ifdef E2BIG set_syserr(E2BIG, "E2BIG"); #endif #ifdef ENOEXEC set_syserr(ENOEXEC, "ENOEXEC"); #endif #ifdef EBADF set_syserr(EBADF, "EBADF"); #endif #ifdef ECHILD set_syserr(ECHILD, "ECHILD"); #endif #ifdef EAGAIN set_syserr(EAGAIN, "EAGAIN"); #endif #ifdef ENOMEM set_syserr(ENOMEM, "ENOMEM"); #endif #ifdef EACCES set_syserr(EACCES, "EACCES"); #endif #ifdef EFAULT set_syserr(EFAULT, "EFAULT"); #endif #ifdef ENOTBLK set_syserr(ENOTBLK, "ENOTBLK"); #endif #ifdef EBUSY set_syserr(EBUSY, "EBUSY"); #endif #ifdef EEXIST set_syserr(EEXIST, "EEXIST"); #endif #ifdef EXDEV set_syserr(EXDEV, "EXDEV"); #endif #ifdef ENODEV set_syserr(ENODEV, "ENODEV"); #endif #ifdef ENOTDIR set_syserr(ENOTDIR, "ENOTDIR"); #endif #ifdef EISDIR set_syserr(EISDIR, "EISDIR"); #endif #ifdef EINVAL set_syserr(EINVAL, "EINVAL"); #endif #ifdef ENFILE set_syserr(ENFILE, "ENFILE"); #endif #ifdef EMFILE set_syserr(EMFILE, "EMFILE"); #endif #ifdef ENOTTY set_syserr(ENOTTY, "ENOTTY"); #endif #ifdef ETXTBSY set_syserr(ETXTBSY, "ETXTBSY"); #endif #ifdef EFBIG set_syserr(EFBIG, "EFBIG"); #endif #ifdef ENOSPC set_syserr(ENOSPC, "ENOSPC"); #endif #ifdef ESPIPE set_syserr(ESPIPE, "ESPIPE"); #endif #ifdef EROFS set_syserr(EROFS, "EROFS"); #endif #ifdef EMLINK set_syserr(EMLINK, "EMLINK"); #endif #ifdef EPIPE set_syserr(EPIPE, "EPIPE"); #endif #ifdef EDOM set_syserr(EDOM, "EDOM"); #endif #ifdef ERANGE set_syserr(ERANGE, "ERANGE"); #endif #ifdef EDEADLK set_syserr(EDEADLK, "EDEADLK"); #endif #ifdef ENAMETOOLONG set_syserr(ENAMETOOLONG, "ENAMETOOLONG"); #endif #ifdef ENOLCK set_syserr(ENOLCK, "ENOLCK"); #endif #ifdef ENOSYS set_syserr(ENOSYS, "ENOSYS"); #endif #ifdef ENOTEMPTY set_syserr(ENOTEMPTY, "ENOTEMPTY"); #endif #ifdef ELOOP set_syserr(ELOOP, "ELOOP"); #endif #ifdef EWOULDBLOCK set_syserr(EWOULDBLOCK, "EWOULDBLOCK"); #endif #ifdef ENOMSG set_syserr(ENOMSG, "ENOMSG"); #endif #ifdef EIDRM set_syserr(EIDRM, "EIDRM"); #endif #ifdef ECHRNG set_syserr(ECHRNG, "ECHRNG"); #endif #ifdef EL2NSYNC set_syserr(EL2NSYNC, "EL2NSYNC"); #endif #ifdef EL3HLT set_syserr(EL3HLT, "EL3HLT"); #endif #ifdef EL3RST set_syserr(EL3RST, "EL3RST"); #endif #ifdef ELNRNG set_syserr(ELNRNG, "ELNRNG"); #endif #ifdef EUNATCH set_syserr(EUNATCH, "EUNATCH"); #endif #ifdef ENOCSI set_syserr(ENOCSI, "ENOCSI"); #endif #ifdef EL2HLT set_syserr(EL2HLT, "EL2HLT"); #endif #ifdef EBADE set_syserr(EBADE, "EBADE"); #endif #ifdef EBADR set_syserr(EBADR, "EBADR"); #endif #ifdef EXFULL set_syserr(EXFULL, "EXFULL"); #endif #ifdef ENOANO set_syserr(ENOANO, "ENOANO"); #endif #ifdef EBADRQC set_syserr(EBADRQC, "EBADRQC"); #endif #ifdef EBADSLT set_syserr(EBADSLT, "EBADSLT"); #endif #ifdef EDEADLOCK set_syserr(EDEADLOCK, "EDEADLOCK"); #endif #ifdef EBFONT set_syserr(EBFONT, "EBFONT"); #endif #ifdef ENOSTR set_syserr(ENOSTR, "ENOSTR"); #endif #ifdef ENODATA set_syserr(ENODATA, "ENODATA"); #endif #ifdef ETIME set_syserr(ETIME, "ETIME"); #endif #ifdef ENOSR set_syserr(ENOSR, "ENOSR"); #endif #ifdef ENONET set_syserr(ENONET, "ENONET"); #endif #ifdef ENOPKG set_syserr(ENOPKG, "ENOPKG"); #endif #ifdef EREMOTE set_syserr(EREMOTE, "EREMOTE"); #endif #ifdef ENOLINK set_syserr(ENOLINK, "ENOLINK"); #endif #ifdef EADV set_syserr(EADV, "EADV"); #endif #ifdef ESRMNT set_syserr(ESRMNT, "ESRMNT"); #endif #ifdef ECOMM set_syserr(ECOMM, "ECOMM"); #endif #ifdef EPROTO set_syserr(EPROTO, "EPROTO"); #endif #ifdef EMULTIHOP set_syserr(EMULTIHOP, "EMULTIHOP"); #endif #ifdef EDOTDOT set_syserr(EDOTDOT, "EDOTDOT"); #endif #ifdef EBADMSG set_syserr(EBADMSG, "EBADMSG"); #endif #ifdef EOVERFLOW set_syserr(EOVERFLOW, "EOVERFLOW"); #endif #ifdef ENOTUNIQ set_syserr(ENOTUNIQ, "ENOTUNIQ"); #endif #ifdef EBADFD set_syserr(EBADFD, "EBADFD"); #endif #ifdef EREMCHG set_syserr(EREMCHG, "EREMCHG"); #endif #ifdef ELIBACC set_syserr(ELIBACC, "ELIBACC"); #endif #ifdef ELIBBAD set_syserr(ELIBBAD, "ELIBBAD"); #endif #ifdef ELIBSCN set_syserr(ELIBSCN, "ELIBSCN"); #endif #ifdef ELIBMAX set_syserr(ELIBMAX, "ELIBMAX"); #endif #ifdef ELIBEXEC set_syserr(ELIBEXEC, "ELIBEXEC"); #endif #ifdef EILSEQ set_syserr(EILSEQ, "EILSEQ"); #endif #ifdef ERESTART set_syserr(ERESTART, "ERESTART"); #endif #ifdef ESTRPIPE set_syserr(ESTRPIPE, "ESTRPIPE"); #endif #ifdef EUSERS set_syserr(EUSERS, "EUSERS"); #endif #ifdef ENOTSOCK set_syserr(ENOTSOCK, "ENOTSOCK"); #endif #ifdef EDESTADDRREQ set_syserr(EDESTADDRREQ, "EDESTADDRREQ"); #endif #ifdef EMSGSIZE set_syserr(EMSGSIZE, "EMSGSIZE"); #endif #ifdef EPROTOTYPE set_syserr(EPROTOTYPE, "EPROTOTYPE"); #endif #ifdef ENOPROTOOPT set_syserr(ENOPROTOOPT, "ENOPROTOOPT"); #endif #ifdef EPROTONOSUPPORT set_syserr(EPROTONOSUPPORT, "EPROTONOSUPPORT"); #endif #ifdef ESOCKTNOSUPPORT set_syserr(ESOCKTNOSUPPORT, "ESOCKTNOSUPPORT"); #endif #ifdef EOPNOTSUPP set_syserr(EOPNOTSUPP, "EOPNOTSUPP"); #endif #ifdef EPFNOSUPPORT set_syserr(EPFNOSUPPORT, "EPFNOSUPPORT"); #endif #ifdef EAFNOSUPPORT set_syserr(EAFNOSUPPORT, "EAFNOSUPPORT"); #endif #ifdef EADDRINUSE set_syserr(EADDRINUSE, "EADDRINUSE"); #endif #ifdef EADDRNOTAVAIL set_syserr(EADDRNOTAVAIL, "EADDRNOTAVAIL"); #endif #ifdef ENETDOWN set_syserr(ENETDOWN, "ENETDOWN"); #endif #ifdef ENETUNREACH set_syserr(ENETUNREACH, "ENETUNREACH"); #endif #ifdef ENETRESET set_syserr(ENETRESET, "ENETRESET"); #endif #ifdef ECONNABORTED set_syserr(ECONNABORTED, "ECONNABORTED"); #endif #ifdef ECONNRESET set_syserr(ECONNRESET, "ECONNRESET"); #endif #ifdef ENOBUFS set_syserr(ENOBUFS, "ENOBUFS"); #endif #ifdef EISCONN set_syserr(EISCONN, "EISCONN"); #endif #ifdef ENOTCONN set_syserr(ENOTCONN, "ENOTCONN"); #endif #ifdef ESHUTDOWN set_syserr(ESHUTDOWN, "ESHUTDOWN"); #endif #ifdef ETOOMANYREFS set_syserr(ETOOMANYREFS, "ETOOMANYREFS"); #endif #ifdef ETIMEDOUT set_syserr(ETIMEDOUT, "ETIMEDOUT"); #endif #ifdef ECONNREFUSED set_syserr(ECONNREFUSED, "ECONNREFUSED"); #endif #ifdef EHOSTDOWN set_syserr(EHOSTDOWN, "EHOSTDOWN"); #endif #ifdef EHOSTUNREACH set_syserr(EHOSTUNREACH, "EHOSTUNREACH"); #endif #ifdef EALREADY set_syserr(EALREADY, "EALREADY"); #endif #ifdef EINPROGRESS set_syserr(EINPROGRESS, "EINPROGRESS"); #endif #ifdef ESTALE set_syserr(ESTALE, "ESTALE"); #endif #ifdef EUCLEAN set_syserr(EUCLEAN, "EUCLEAN"); #endif #ifdef ENOTNAM set_syserr(ENOTNAM, "ENOTNAM"); #endif #ifdef ENAVAIL set_syserr(ENAVAIL, "ENAVAIL"); #endif #ifdef EISNAM set_syserr(EISNAM, "EISNAM"); #endif #ifdef EREMOTEIO set_syserr(EREMOTEIO, "EREMOTEIO"); #endif #ifdef EDQUOT set_syserr(EDQUOT, "EDQUOT"); #endif } static void err_append(s) const char *s; { extern VALUE ruby_errinfo; if (ruby_in_eval) { if (NIL_P(ruby_errinfo)) { ruby_errinfo = rb_exc_new2(rb_eSyntaxError, s); } else { VALUE str = rb_obj_as_string(ruby_errinfo); rb_str_cat2(str, "\n"); rb_str_cat2(str, s); ruby_errinfo = rb_exc_new3(rb_eSyntaxError, str); } } else { rb_write_error(s); rb_write_error("\n"); } } /********************************************************************** eval.c - $Author: wyhaines $ $Date: 2009-08-20 19:22:00 +0200 (Thu, 20 Aug 2009) $ created at: Thu Jun 10 14:22:17 JST 1993 Copyright (C) 1993-2003 Yukihiro Matsumoto Copyright (C) 2000 Network Applied Communication Laboratory, Inc. Copyright (C) 2000 Information-technology Promotion Agency, Japan **********************************************************************/ #include "ruby.h" #include "node.h" #include "env.h" #include "util.h" #include "rubysig.h" #ifdef HAVE_STDLIB_H #include #endif #ifndef EXIT_SUCCESS #define EXIT_SUCCESS 0 #endif #ifndef EXIT_FAILURE #define EXIT_FAILURE 1 #endif #include #include "st.h" #include "dln.h" #ifdef __APPLE__ #include #endif /* Make alloca work the best possible way. */ #ifdef __GNUC__ # ifndef atarist # ifndef alloca # define alloca __builtin_alloca # endif # endif /* atarist */ #else # ifdef HAVE_ALLOCA_H # include # else # ifndef _AIX # ifndef alloca /* predefined by HP cc +Olibcalls */ void *alloca (); # endif # endif /* AIX */ # endif /* HAVE_ALLOCA_H */ #endif /* __GNUC__ */ #ifdef HAVE_STDARG_PROTOTYPES #include #define va_init_list(a,b) va_start(a,b) #else #include #define va_init_list(a,b) va_start(a) #endif #ifndef HAVE_STRING_H char *strrchr _((const char*,const char)); #endif #ifdef HAVE_UNISTD_H #include #endif #include #if defined(HAVE_FCNTL_H) || defined(_WIN32) #include #elif defined(HAVE_SYS_FCNTL_H) #include #endif #ifdef __CYGWIN__ #include #endif #if defined(__BEOS__) && !defined(BONE) #include #endif #ifdef __MACOS__ #include "macruby_private.h" #endif #ifdef __VMS #include "vmsruby_private.h" #endif #ifdef USE_CONTEXT NORETURN(static void rb_jump_context(rb_jmpbuf_t, int)); static inline void rb_jump_context(env, val) rb_jmpbuf_t env; int val; { env->status = val; setcontext(&env->context); abort(); /* ensure noreturn */ } /* * PRE_GETCONTEXT and POST_GETCONTEXT is a magic for getcontext, gcc, * IA64 register stack and SPARC register window combination problem. * * Assume following code sequence. * * 1. set a register in the register stack/window such as r32/l0. * 2. call getcontext. * 3. use the register. * 4. update the register for other use. * 5. call setcontext indirectly (or directly). * * This code should be run as 1->2->3->4->5->3->4. * But after second getcontext return (second 3), * the register is broken (updated). * It's because getcontext/setcontext doesn't preserve the content of the * register stack/window. * * setjmp also doesn't preserve the content of the register stack/window. * But it has not the problem because gcc knows setjmp may return twice. * gcc detects setjmp and generates setjmp safe code. * * So setjmp calls before and after the getcontext call makes the code * somewhat safe. * It fix the problem on IA64. * It is not required that setjmp is called at run time, since the problem is * register usage. * * Since the magic setjmp is not enough for SPARC, * inline asm is used to prohibit registers in register windows. * * Since the problem is fixed at gcc 4.0.3, the magic is applied only for * prior versions of gcc. * http://gcc.gnu.org/bugzilla/show_bug.cgi?id=21957 * http://gcc.gnu.org/bugzilla/show_bug.cgi?id=22127 */ # define GCC_VERSION_BEFORE(major, minor, patchlevel) \ (defined(__GNUC__) && !defined(__INTEL_COMPILER) && \ ((__GNUC__ < (major)) || \ (__GNUC__ == (major) && __GNUC_MINOR__ < (minor)) || \ (__GNUC__ == (major) && __GNUC_MINOR__ == (minor) && __GNUC_PATCHLEVEL__ < (patchlevel)))) # if GCC_VERSION_BEFORE(4,0,3) && (defined(sparc) || defined(__sparc__)) # ifdef __pic__ /* * %l7 is excluded for PIC because it is PIC register. * http://lists.freebsd.org/pipermail/freebsd-sparc64/2006-January/003739.html */ # define PRE_GETCONTEXT \ ({ __asm__ volatile ("" : : : \ "%o0", "%o1", "%o2", "%o3", "%o4", "%o5", "%o7", \ "%l0", "%l1", "%l2", "%l3", "%l4", "%l5", "%l6", \ "%i0", "%i1", "%i2", "%i3", "%i4", "%i5", "%i7"); }) # else # define PRE_GETCONTEXT \ ({ __asm__ volatile ("" : : : \ "%o0", "%o1", "%o2", "%o3", "%o4", "%o5", "%o7", \ "%l0", "%l1", "%l2", "%l3", "%l4", "%l5", "%l6", "%l7", \ "%i0", "%i1", "%i2", "%i3", "%i4", "%i5", "%i7"); }) # endif # define POST_GETCONTEXT PRE_GETCONTEXT # elif GCC_VERSION_BEFORE(4,0,3) && defined(__ia64) static jmp_buf function_call_may_return_twice_jmp_buf; int function_call_may_return_twice_false_1 = 0; int function_call_may_return_twice_false_2 = 0; # define PRE_GETCONTEXT \ (function_call_may_return_twice_false_1 ? \ setjmp(function_call_may_return_twice_jmp_buf) : \ 0) # define POST_GETCONTEXT \ (function_call_may_return_twice_false_2 ? \ setjmp(function_call_may_return_twice_jmp_buf) : \ 0) # elif defined(__FreeBSD__) && __FreeBSD__ < 7 /* * workaround for FreeBSD/i386 getcontext/setcontext bug. * clear the carry flag by (0 ? ... : ...). * FreeBSD PR 92110 http://www.freebsd.org/cgi/query-pr.cgi?pr=92110 * [ruby-dev:28263] */ static int volatile freebsd_clear_carry_flag = 0; # define PRE_GETCONTEXT \ (freebsd_clear_carry_flag ? (freebsd_clear_carry_flag = 0) : 0) # endif # ifndef PRE_GETCONTEXT # define PRE_GETCONTEXT 0 # endif # ifndef POST_GETCONTEXT # define POST_GETCONTEXT 0 # endif # define ruby_longjmp(env, val) rb_jump_context(env, val) # define ruby_setjmp(just_before_setjmp, j) ((j)->status = 0, \ (just_before_setjmp), \ PRE_GETCONTEXT, \ getcontext(&(j)->context), \ POST_GETCONTEXT, \ (j)->status) #else # if !defined(setjmp) && defined(HAVE__SETJMP) # define ruby_setjmp(just_before_setjmp, env) \ ((just_before_setjmp), _setjmp(env)) # define ruby_longjmp(env,val) _longjmp(env,val) # else # define ruby_setjmp(just_before_setjmp, env) \ ((just_before_setjmp), setjmp(env)) # define ruby_longjmp(env,val) longjmp(env,val) # endif #endif #include #include #include #if defined(__VMS) #pragma nostandard #endif #ifdef HAVE_SYS_SELECT_H #include #endif #include VALUE rb_cProc; VALUE rb_cBinding; static VALUE proc_invoke _((VALUE,VALUE,VALUE,VALUE)); static VALUE rb_f_binding _((VALUE)); static void rb_f_END _((void)); static VALUE rb_f_block_given_p _((void)); static VALUE block_pass _((VALUE,NODE*)); VALUE rb_cMethod; static VALUE method_call _((int, VALUE*, VALUE)); VALUE rb_cUnboundMethod; static VALUE umethod_bind _((VALUE, VALUE)); static VALUE rb_mod_define_method _((int, VALUE*, VALUE)); NORETURN(static void rb_raise_jump _((VALUE))); static VALUE rb_make_exception _((int argc, VALUE *argv)); static int scope_vmode; #define SCOPE_PUBLIC 0 #define SCOPE_PRIVATE 1 #define SCOPE_PROTECTED 2 #define SCOPE_MODFUNC 5 #define SCOPE_MASK 7 #define SCOPE_SET(f) (scope_vmode=(f)) #define SCOPE_TEST(f) (scope_vmode&(f)) VALUE (*ruby_sandbox_save)_((rb_thread_t)); VALUE (*ruby_sandbox_restore)_((rb_thread_t)); NODE* ruby_current_node; int ruby_safe_level = 0; /* safe-level: 0 - strings from streams/environment/ARGV are tainted (default) 1 - no dangerous operation by tainted value 2 - process/file operations prohibited 3 - all generated objects are tainted 4 - no global (non-tainted) variable modification/no direct output */ static VALUE safe_getter _((void)); static void safe_setter _((VALUE val)); void rb_secure(level) int level; { if (level <= ruby_safe_level) { if (ruby_frame->last_func) { rb_raise(rb_eSecurityError, "Insecure operation `%s' at level %d", rb_id2name(ruby_frame->last_func), ruby_safe_level); } else { rb_raise(rb_eSecurityError, "Insecure operation at level %d", ruby_safe_level); } } } void rb_secure_update(obj) VALUE obj; { if (!OBJ_TAINTED(obj)) rb_secure(4); } void rb_check_safe_obj(x) VALUE x; { if (ruby_safe_level > 0 && OBJ_TAINTED(x)){ if (ruby_frame->last_func) { rb_raise(rb_eSecurityError, "Insecure operation - %s", rb_id2name(ruby_frame->last_func)); } else { rb_raise(rb_eSecurityError, "Insecure operation: -r"); } } rb_secure(4); } void rb_check_safe_str(x) VALUE x; { rb_check_safe_obj(x); if (TYPE(x)!= T_STRING) { rb_raise(rb_eTypeError, "wrong argument type %s (expected String)", rb_obj_classname(x)); } } NORETURN(static void print_undef _((VALUE, ID))); static void print_undef(klass, id) VALUE klass; ID id; { rb_name_error(id, "undefined method `%s' for %s `%s'", rb_id2name(id), (TYPE(klass) == T_MODULE) ? "module" : "class", rb_class2name(klass)); } static ID removed, singleton_removed, undefined, singleton_undefined; #define CACHE_SIZE 0x800 #define CACHE_MASK 0x7ff #define EXPR1(c,m) ((((c)>>3)^(m))&CACHE_MASK) struct cache_entry { /* method hash table. */ ID mid; /* method's id */ ID mid0; /* method's original id */ VALUE klass; /* receiver's class */ VALUE origin; /* where method defined */ NODE *method; int noex; }; static struct cache_entry cache[CACHE_SIZE]; static int ruby_running = 0; void rb_clear_cache() { struct cache_entry *ent, *end; if (!ruby_running) return; ent = cache; end = ent + CACHE_SIZE; while (ent < end) { ent->mid = 0; ent++; } } static void rb_clear_cache_for_undef(klass, id) VALUE klass; ID id; { struct cache_entry *ent, *end; if (!ruby_running) return; ent = cache; end = ent + CACHE_SIZE; while (ent < end) { if (ent->mid == id && RCLASS(ent->origin)->m_tbl == RCLASS(klass)->m_tbl) { ent->mid = 0; } ent++; } } static void rb_clear_cache_by_id(id) ID id; { struct cache_entry *ent, *end; if (!ruby_running) return; ent = cache; end = ent + CACHE_SIZE; while (ent < end) { if (ent->mid == id) { ent->mid = 0; } ent++; } } void rb_clear_cache_by_class(klass) VALUE klass; { struct cache_entry *ent, *end; if (!ruby_running) return; ent = cache; end = ent + CACHE_SIZE; while (ent < end) { if (ent->klass == klass || ent->origin == klass) { ent->mid = 0; } ent++; } } static ID init, eqq, each, aref, aset, match, missing; static ID added, singleton_added; static ID __id__, __send__, respond_to; #define NOEX_TAINTED 8 #define NOEX_SAFE(n) ((n) >> 4) #define NOEX_WITH(n, v) ((n) | (v) << 4) #define NOEX_WITH_SAFE(n) NOEX_WITH(n, ruby_safe_level) void rb_add_method(klass, mid, node, noex) VALUE klass; ID mid; NODE *node; int noex; { NODE *body; if (NIL_P(klass)) klass = rb_cObject; if (ruby_safe_level >= 4 && (klass == rb_cObject || !OBJ_TAINTED(klass))) { rb_raise(rb_eSecurityError, "Insecure: can't define method"); } if (!FL_TEST(klass, FL_SINGLETON) && node && nd_type(node) != NODE_ZSUPER && (mid == rb_intern("initialize" )|| mid == rb_intern("initialize_copy"))) { noex = NOEX_PRIVATE | noex; } else if (FL_TEST(klass, FL_SINGLETON) && node && nd_type(node) == NODE_CFUNC && mid == rb_intern("allocate")) { rb_warn("defining %s.allocate is deprecated; use rb_define_alloc_func()", rb_class2name(rb_iv_get(klass, "__attached__"))); mid = ID_ALLOCATOR; } if (OBJ_FROZEN(klass)) rb_error_frozen("class/module"); rb_clear_cache_by_id(mid); body = NEW_METHOD(node, NOEX_WITH_SAFE(noex)); st_insert(RCLASS(klass)->m_tbl, mid, (st_data_t)body); if (node && mid != ID_ALLOCATOR && ruby_running) { if (FL_TEST(klass, FL_SINGLETON)) { rb_funcall(rb_iv_get(klass, "__attached__"), singleton_added, 1, ID2SYM(mid)); } else { rb_funcall(klass, added, 1, ID2SYM(mid)); } } } void rb_define_alloc_func(klass, func) VALUE klass; VALUE (*func) _((VALUE)); { Check_Type(klass, T_CLASS); rb_add_method(rb_singleton_class(klass), ID_ALLOCATOR, NEW_CFUNC(func, 0), NOEX_PRIVATE); } void rb_undef_alloc_func(klass) VALUE klass; { Check_Type(klass, T_CLASS); rb_add_method(rb_singleton_class(klass), ID_ALLOCATOR, 0, NOEX_UNDEF); } static NODE* search_method(klass, id, origin) VALUE klass, *origin; ID id; { st_data_t body; if (!klass) return 0; while (!st_lookup(RCLASS(klass)->m_tbl, id, &body)) { klass = RCLASS(klass)->super; if (!klass) return 0; } if (origin) *origin = klass; return (NODE *)body; } static NODE* rb_get_method_body(klassp, idp, noexp) VALUE *klassp; ID *idp; int *noexp; { ID id = *idp; VALUE klass = *klassp; VALUE origin; NODE * volatile body; struct cache_entry *ent; if ((body = search_method(klass, id, &origin)) == 0 || !body->nd_body) { /* store empty info in cache */ ent = cache + EXPR1(klass, id); ent->klass = klass; ent->origin = klass; ent->mid = ent->mid0 = id; ent->noex = 0; ent->method = 0; return 0; } if (ruby_running) { /* store in cache */ ent = cache + EXPR1(klass, id); ent->klass = klass; ent->noex = body->nd_noex; if (noexp) *noexp = body->nd_noex; body = body->nd_body; if (nd_type(body) == NODE_FBODY) { ent->mid = id; *klassp = body->nd_orig; ent->origin = body->nd_orig; *idp = ent->mid0 = body->nd_mid; body = ent->method = body->nd_head; } else { *klassp = origin; ent->origin = origin; ent->mid = ent->mid0 = id; ent->method = body; } } else { if (noexp) *noexp = body->nd_noex; body = body->nd_body; if (nd_type(body) == NODE_FBODY) { *klassp = body->nd_orig; *idp = body->nd_mid; body = body->nd_head; } else { *klassp = origin; } } return body; } NODE* rb_method_node(klass, id) VALUE klass; ID id; { int noex; return rb_get_method_body(&klass, &id, &noex); } static void remove_method(klass, mid) VALUE klass; ID mid; { st_data_t data; NODE *body = 0; if (klass == rb_cObject) { rb_secure(4); } if (ruby_safe_level >= 4 && !OBJ_TAINTED(klass)) { rb_raise(rb_eSecurityError, "Insecure: can't remove method"); } if (OBJ_FROZEN(klass)) rb_error_frozen("class/module"); if (mid == __id__ || mid == __send__ || mid == init) { rb_warn("removing `%s' may cause serious problem", rb_id2name(mid)); } if (st_lookup(RCLASS(klass)->m_tbl, mid, &data)) { body = (NODE *)data; if (!body || !body->nd_body) body = 0; else { st_delete(RCLASS(klass)->m_tbl, &mid, &data); } } if (!body) { rb_name_error(mid, "method `%s' not defined in %s", rb_id2name(mid), rb_class2name(klass)); } rb_clear_cache_for_undef(klass, mid); if (FL_TEST(klass, FL_SINGLETON)) { rb_funcall(rb_iv_get(klass, "__attached__"), singleton_removed, 1, ID2SYM(mid)); } else { rb_funcall(klass, removed, 1, ID2SYM(mid)); } } void rb_remove_method(klass, name) VALUE klass; const char *name; { remove_method(klass, rb_intern(name)); } /* * call-seq: * remove_method(symbol) => self * * Removes the method identified by _symbol_ from the current * class. For an example, see Module.undef_method. */ static VALUE rb_mod_remove_method(argc, argv, mod) int argc; VALUE *argv; VALUE mod; { int i; for (i=0; ind_body) { print_undef(klass, name); } if (body->nd_noex != noex) { if (klass == origin) { body->nd_noex = noex; } else { rb_add_method(klass, name, NEW_ZSUPER(), noex); } } } int rb_method_boundp(klass, id, ex) VALUE klass; ID id; int ex; { struct cache_entry *ent; int noex; /* is it in the method cache? */ ent = cache + EXPR1(klass, id); if (ent->mid == id && ent->klass == klass) { if (ex && (ent->noex & NOEX_PRIVATE)) return Qfalse; if (!ent->method) return Qfalse; return Qtrue; } if (rb_get_method_body(&klass, &id, &noex)) { if (ex && (noex & NOEX_PRIVATE)) return Qfalse; return Qtrue; } return Qfalse; } void rb_attr(klass, id, read, write, ex) VALUE klass; ID id; int read, write, ex; { const char *name; char *buf; ID attriv; int noex; size_t len; if (!ex) noex = NOEX_PUBLIC; else { if (SCOPE_TEST(SCOPE_PRIVATE)) { noex = NOEX_PRIVATE; rb_warning((scope_vmode == SCOPE_MODFUNC) ? "attribute accessor as module_function" : "private attribute?"); } else if (SCOPE_TEST(SCOPE_PROTECTED)) { noex = NOEX_PROTECTED; } else { noex = NOEX_PUBLIC; } } if (!rb_is_local_id(id) && !rb_is_const_id(id)) { rb_name_error(id, "invalid attribute name `%s'", rb_id2name(id)); } name = rb_id2name(id); if (!name) { rb_raise(rb_eArgError, "argument needs to be symbol or string"); } len = strlen(name)+2; buf = ALLOCA_N(char,len); snprintf(buf, len, "@%s", name); attriv = rb_intern(buf); if (read) { rb_add_method(klass, id, NEW_IVAR(attriv), noex); } if (write) { rb_add_method(klass, rb_id_attrset(id), NEW_ATTRSET(attriv), noex); } } extern int ruby_in_compile; VALUE ruby_errinfo = Qnil; extern NODE *ruby_eval_tree_begin; extern NODE *ruby_eval_tree; extern int ruby_nerrs; VALUE rb_eLocalJumpError; VALUE rb_eSysStackError; extern VALUE ruby_top_self; struct FRAME *ruby_frame; struct SCOPE *ruby_scope; static struct FRAME *top_frame; static struct SCOPE *top_scope; static unsigned long frame_unique = 0; #define PUSH_FRAME() do { \ volatile struct FRAME _frame; \ _frame.prev = ruby_frame; \ _frame.tmp = 0; \ _frame.node = ruby_current_node; \ _frame.iter = ruby_iter->iter; \ _frame.argc = 0; \ _frame.flags = 0; \ _frame.uniq = frame_unique++; \ ruby_frame = &_frame #define POP_FRAME() \ ruby_current_node = _frame.node; \ ruby_frame = _frame.prev; \ } while (0) struct BLOCK { NODE *var; NODE *body; VALUE self; struct FRAME frame; struct SCOPE *scope; VALUE klass; NODE *cref; int iter; int vmode; int flags; int uniq; struct RVarmap *dyna_vars; VALUE orig_thread; VALUE wrapper; VALUE block_obj; struct BLOCK *outer; struct BLOCK *prev; }; #define BLOCK_D_SCOPE 1 #define BLOCK_LAMBDA 2 static struct BLOCK *ruby_block; static unsigned long block_unique = 1; #define PUSH_BLOCK(v,b) do { \ struct BLOCK _block; \ _block.var = (v); \ _block.body = (b); \ _block.self = self; \ _block.frame = *ruby_frame; \ _block.klass = ruby_class; \ _block.cref = ruby_cref; \ _block.frame.node = ruby_current_node;\ _block.scope = ruby_scope; \ _block.prev = ruby_block; \ _block.outer = ruby_block; \ _block.iter = ruby_iter->iter; \ _block.vmode = scope_vmode; \ _block.flags = BLOCK_D_SCOPE; \ _block.dyna_vars = ruby_dyna_vars; \ _block.wrapper = ruby_wrapper; \ _block.block_obj = 0; \ _block.uniq = (b)?block_unique++:0; \ if (b) { \ prot_tag->blkid = _block.uniq; \ } \ ruby_block = &_block #define POP_BLOCK() \ ruby_block = _block.prev; \ } while (0) struct RVarmap *ruby_dyna_vars; #define PUSH_VARS() do { \ struct RVarmap * volatile _old; \ _old = ruby_dyna_vars; \ ruby_dyna_vars = 0 #define POP_VARS() \ if (_old && (ruby_scope->flags & SCOPE_DONT_RECYCLE)) {\ if (RBASIC(_old)->flags) /* unless it's already recycled */ \ FL_SET(_old, DVAR_DONT_RECYCLE); \ }\ ruby_dyna_vars = _old; \ } while (0) #define DVAR_DONT_RECYCLE FL_USER2 #define DMETHOD_P() (ruby_frame->flags & FRAME_DMETH) static struct RVarmap* new_dvar(id, value, prev) ID id; VALUE value; struct RVarmap *prev; { NEWOBJ(vars, struct RVarmap); OBJSETUP(vars, 0, T_VARMAP); vars->id = id; vars->val = value; vars->next = prev; return vars; } VALUE rb_dvar_defined(id) ID id; { struct RVarmap *vars = ruby_dyna_vars; while (vars) { if (vars->id == id) return Qtrue; vars = vars->next; } return Qfalse; } VALUE rb_dvar_curr(id) ID id; { struct RVarmap *vars = ruby_dyna_vars; while (vars) { if (vars->id == 0) break; if (vars->id == id) return Qtrue; vars = vars->next; } return Qfalse; } VALUE rb_dvar_ref(id) ID id; { struct RVarmap *vars = ruby_dyna_vars; while (vars) { if (vars->id == id) { return vars->val; } vars = vars->next; } return Qnil; } void rb_dvar_push(id, value) ID id; VALUE value; { ruby_dyna_vars = new_dvar(id, value, ruby_dyna_vars); } static void dvar_asgn_internal(id, value, curr) ID id; VALUE value; int curr; { int n = 0; struct RVarmap *vars = ruby_dyna_vars; while (vars) { if (curr && vars->id == 0) { /* first null is a dvar header */ n++; if (n == 2) break; } if (vars->id == id) { vars->val = value; return; } vars = vars->next; } if (!ruby_dyna_vars) { ruby_dyna_vars = new_dvar(id, value, 0); } else { vars = new_dvar(id, value, ruby_dyna_vars->next); ruby_dyna_vars->next = vars; } } static inline void dvar_asgn(id, value) ID id; VALUE value; { dvar_asgn_internal(id, value, 0); } static inline void dvar_asgn_curr(id, value) ID id; VALUE value; { dvar_asgn_internal(id, value, 1); } VALUE * rb_svar(cnt) int cnt; { struct RVarmap *vars = ruby_dyna_vars; ID id; if (!ruby_scope->local_tbl) return NULL; if (cnt >= ruby_scope->local_tbl[0]) return NULL; id = ruby_scope->local_tbl[cnt+1]; while (vars) { if (vars->id == id) return &vars->val; vars = vars->next; } if (ruby_scope->local_vars == 0) return NULL; return &ruby_scope->local_vars[cnt]; } struct iter { int iter; struct iter *prev; }; static struct iter *ruby_iter; #define ITER_NOT 0 #define ITER_PRE 1 #define ITER_CUR 2 #define ITER_PAS 3 #define PUSH_ITER(i) do { \ struct iter _iter; \ _iter.prev = ruby_iter; \ _iter.iter = (i); \ ruby_iter = &_iter #define POP_ITER() \ ruby_iter = _iter.prev; \ } while (0) struct tag { rb_jmpbuf_t buf; struct FRAME *frame; struct iter *iter; VALUE tag; VALUE retval; struct SCOPE *scope; VALUE dst; struct tag *prev; int blkid; }; static struct tag *prot_tag; #define PUSH_TAG(ptag) do { \ struct tag _tag; \ _tag.retval = Qnil; \ _tag.frame = ruby_frame; \ _tag.iter = ruby_iter; \ _tag.prev = prot_tag; \ _tag.scope = ruby_scope; \ _tag.tag = ptag; \ _tag.dst = 0; \ _tag.blkid = 0; \ prot_tag = &_tag #define PROT_NONE Qfalse /* 0 */ #define PROT_THREAD Qtrue /* 2 */ #define PROT_FUNC INT2FIX(0) /* 1 */ #define PROT_LOOP INT2FIX(1) /* 3 */ #define PROT_LAMBDA INT2FIX(2) /* 5 */ #define PROT_YIELD INT2FIX(3) /* 7 */ #define EXEC_TAG() (FLUSH_REGISTER_WINDOWS, ruby_setjmp(((void)0), prot_tag->buf)) #define JUMP_TAG(st) do { \ ruby_frame = prot_tag->frame; \ ruby_iter = prot_tag->iter; \ ruby_longjmp(prot_tag->buf,(st)); \ } while (0) #define POP_TAG() \ prot_tag = _tag.prev; \ } while (0) #define TAG_DST() (_tag.dst == (VALUE)ruby_frame->uniq) #define TAG_RETURN 0x1 #define TAG_BREAK 0x2 #define TAG_NEXT 0x3 #define TAG_RETRY 0x4 #define TAG_REDO 0x5 #define TAG_RAISE 0x6 #define TAG_THROW 0x7 #define TAG_FATAL 0x8 #define TAG_MASK 0xf VALUE ruby_class; static VALUE ruby_wrapper; /* security wrapper */ #define PUSH_CLASS(c) do { \ volatile VALUE _class = ruby_class; \ ruby_class = (c) #define POP_CLASS() ruby_class = _class; \ } while (0) NODE *ruby_cref = 0; NODE *ruby_top_cref; #define PUSH_CREF(c) ruby_cref = NEW_CREF(c,ruby_cref) #define POP_CREF() ruby_cref = ruby_cref->nd_next #define PUSH_SCOPE() do { \ volatile int _vmode = scope_vmode; \ struct SCOPE * volatile _old; \ NEWOBJ(_scope, struct SCOPE); \ OBJSETUP(_scope, 0, T_SCOPE); \ _scope->local_tbl = 0; \ _scope->local_vars = 0; \ _scope->flags = 0; \ _old = ruby_scope; \ ruby_scope = _scope; \ scope_vmode = SCOPE_PUBLIC rb_thread_t rb_curr_thread; rb_thread_t rb_main_thread; #define main_thread rb_main_thread #define curr_thread rb_curr_thread static void scope_dup _((struct SCOPE *)); #define POP_SCOPE() \ if (ruby_scope->flags & SCOPE_DONT_RECYCLE) {\ if (_old) scope_dup(_old); \ } \ if (!(ruby_scope->flags & SCOPE_MALLOC)) {\ ruby_scope->local_vars = 0; \ ruby_scope->local_tbl = 0; \ if (!(ruby_scope->flags & SCOPE_DONT_RECYCLE) && \ ruby_scope != top_scope) { \ rb_gc_force_recycle((VALUE)ruby_scope);\ } \ } \ ruby_scope->flags |= SCOPE_NOSTACK; \ ruby_scope = _old; \ scope_vmode = _vmode; \ } while (0) static VALUE rb_eval _((VALUE,NODE*)); static VALUE eval _((VALUE,VALUE,VALUE,char*,int)); static NODE *compile _((VALUE, char*, int)); static VALUE rb_yield_0 _((VALUE, VALUE, VALUE, int, int)); #define YIELD_LAMBDA_CALL 1 #define YIELD_PROC_CALL 2 #define YIELD_PUBLIC_DEF 4 #define YIELD_FUNC_AVALUE 1 #define YIELD_FUNC_SVALUE 2 static VALUE rb_call _((VALUE,VALUE,ID,int,const VALUE*,int,VALUE)); static VALUE module_setup _((VALUE,NODE*)); static VALUE massign _((VALUE,NODE*,VALUE,int)); static void assign _((VALUE,NODE*,VALUE,int)); typedef struct event_hook { rb_event_hook_func_t func; rb_event_t events; struct event_hook *next; } rb_event_hook_t; static rb_event_hook_t *event_hooks; #define EXEC_EVENT_HOOK(event, node, self, id, klass) \ do { \ rb_event_hook_t *hook = event_hooks; \ rb_event_hook_func_t hook_func; \ rb_event_t events; \ \ while (hook) { \ hook_func = hook->func; \ events = hook->events; \ hook = hook->next; \ if (events & event) \ (*hook_func)(event, node, self, id, klass); \ } \ } while (0) static VALUE trace_func = 0; static int tracing = 0; static void call_trace_func _((rb_event_t,NODE*,VALUE,ID,VALUE)); #if 0 #define SET_CURRENT_SOURCE() (ruby_sourcefile = ruby_current_node->nd_file, \ ruby_sourceline = nd_line(ruby_current_node)) #else #define SET_CURRENT_SOURCE() ((void)0) #endif void ruby_set_current_source() { if (ruby_current_node) { ruby_sourcefile = ruby_current_node->nd_file; ruby_sourceline = nd_line(ruby_current_node); } } static void #ifdef HAVE_STDARG_PROTOTYPES warn_printf(const char *fmt, ...) #else warn_printf(fmt, va_alist) const char *fmt; va_dcl #endif { char buf[BUFSIZ]; va_list args; va_init_list(args, fmt); vsnprintf(buf, BUFSIZ, fmt, args); va_end(args); rb_write_error(buf); } #define warn_print(x) rb_write_error(x) #define warn_print2(x,l) rb_write_error2(x,l) static void error_pos() { ruby_set_current_source(); if (ruby_sourcefile) { if (ruby_frame->last_func) { warn_printf("%s:%d:in `%s'", ruby_sourcefile, ruby_sourceline, rb_id2name(ruby_frame->orig_func)); } else if (ruby_sourceline == 0) { warn_printf("%s", ruby_sourcefile); } else { warn_printf("%s:%d", ruby_sourcefile, ruby_sourceline); } } } VALUE rb_check_backtrace(VALUE); static VALUE get_backtrace(info) VALUE info; { if (NIL_P(info)) return Qnil; info = rb_funcall(info, rb_intern("backtrace"), 0); if (NIL_P(info)) return Qnil; return rb_check_backtrace(info); } static void set_backtrace(info, bt) VALUE info, bt; { rb_funcall(info, rb_intern("set_backtrace"), 1, bt); } static void error_print() { VALUE errat = Qnil; /* OK */ volatile VALUE eclass, e; char *einfo; long elen; if (NIL_P(ruby_errinfo)) return; PUSH_TAG(PROT_NONE); if (EXEC_TAG() == 0) { errat = get_backtrace(ruby_errinfo); } else { errat = Qnil; } if (EXEC_TAG()) goto error; if (NIL_P(errat)){ ruby_set_current_source(); if (ruby_sourcefile) warn_printf("%s:%d", ruby_sourcefile, ruby_sourceline); else warn_printf("%d", ruby_sourceline); } else if (RARRAY(errat)->len == 0) { error_pos(); } else { VALUE mesg = RARRAY(errat)->ptr[0]; if (NIL_P(mesg)) error_pos(); else { warn_print2(RSTRING(mesg)->ptr, RSTRING(mesg)->len); } } eclass = CLASS_OF(ruby_errinfo); if (EXEC_TAG() == 0) { e = rb_funcall(ruby_errinfo, rb_intern("message"), 0, 0); StringValue(e); einfo = RSTRING(e)->ptr; elen = RSTRING(e)->len; } else { einfo = ""; elen = 0; } if (EXEC_TAG()) goto error; if (eclass == rb_eRuntimeError && elen == 0) { warn_print(": unhandled exception\n"); } else { VALUE epath; epath = rb_class_name(eclass); if (elen == 0) { warn_print(": "); warn_print2(RSTRING(epath)->ptr, RSTRING(epath)->len); warn_print("\n"); } else { char *tail = 0; long len = elen; if (RSTRING(epath)->ptr[0] == '#') epath = 0; if ((tail = memchr(einfo, '\n', elen)) != 0) { len = tail - einfo; tail++; /* skip newline */ } warn_print(": "); warn_print2(einfo, len); if (epath) { warn_print(" ("); warn_print2(RSTRING(epath)->ptr, RSTRING(epath)->len); warn_print(")\n"); } if (tail && elen>len+1) { warn_print2(tail, elen-len-1); if (einfo[elen-1] != '\n') warn_print2("\n", 1); } } } if (!NIL_P(errat)) { long i; struct RArray *ep = RARRAY(errat); #define TRACE_MAX (TRACE_HEAD+TRACE_TAIL+5) #define TRACE_HEAD 8 #define TRACE_TAIL 5 ep = RARRAY(errat); for (i=1; ilen; i++) { if (TYPE(ep->ptr[i]) == T_STRING) { warn_printf("\tfrom %s\n", RSTRING(ep->ptr[i])->ptr); } if (i == TRACE_HEAD && ep->len > TRACE_MAX) { warn_printf("\t ... %ld levels...\n", ep->len - TRACE_HEAD - TRACE_TAIL); i = ep->len - TRACE_TAIL; } } } error: POP_TAG(); } #if defined(__APPLE__) #define environ (*_NSGetEnviron()) #elif !defined(_WIN32) && !defined(__MACOS__) || defined(_WIN32_WCE) extern char **environ; #endif char **rb_origenviron; void rb_call_inits _((void)); void Init_stack _((VALUE*)); void Init_heap _((void)); void Init_ext _((void)); #ifdef HAVE_NATIVETHREAD static rb_nativethread_t ruby_thid; int is_ruby_native_thread() { return NATIVETHREAD_EQUAL(ruby_thid, NATIVETHREAD_CURRENT()); } # ifdef HAVE_NATIVETHREAD_KILL void ruby_native_thread_kill(sig) int sig; { NATIVETHREAD_KILL(ruby_thid, sig); } # endif #endif void ruby_init() { static int initialized = 0; static struct FRAME frame; static struct iter iter; int state; if (initialized) return; initialized = 1; #ifdef HAVE_NATIVETHREAD ruby_thid = NATIVETHREAD_CURRENT(); #endif ruby_frame = top_frame = &frame; ruby_iter = &iter; #ifdef __MACOS__ rb_origenviron = 0; #else rb_origenviron = environ; #endif Init_stack((void*)&state); Init_heap(); PUSH_SCOPE(); ruby_scope->local_vars = 0; ruby_scope->local_tbl = 0; top_scope = ruby_scope; /* default visibility is private at toplevel */ SCOPE_SET(SCOPE_PRIVATE); PUSH_TAG(PROT_NONE); if ((state = EXEC_TAG()) == 0) { rb_call_inits(); ruby_class = rb_cObject; ruby_frame->self = ruby_top_self; ruby_top_cref = rb_node_newnode(NODE_CREF,rb_cObject,0,0); ruby_cref = ruby_top_cref; rb_define_global_const("TOPLEVEL_BINDING", rb_f_binding(ruby_top_self)); #ifdef __MACOS__ _macruby_init(); #elif defined(__VMS) _vmsruby_init(); #endif ruby_prog_init(); ALLOW_INTS; } POP_TAG(); if (state) { error_print(); exit(EXIT_FAILURE); } POP_SCOPE(); ruby_scope = top_scope; top_scope->flags &= ~SCOPE_NOSTACK; ruby_running = 1; } static VALUE eval_node(self, node) VALUE self; NODE *node; { NODE *beg_tree = ruby_eval_tree_begin; ruby_eval_tree_begin = 0; if (beg_tree) { rb_eval(self, beg_tree); } if (!node) return Qnil; return rb_eval(self, node); } int ruby_in_eval; static void rb_thread_cleanup _((void)); static void rb_thread_wait_other_threads _((void)); static int thread_no_ensure _((void)); static VALUE exception_error; static VALUE sysstack_error; static int sysexit_status(err) VALUE err; { VALUE st = rb_iv_get(err, "status"); return NUM2INT(st); } static int error_handle(ex) int ex; { int status = EXIT_FAILURE; rb_thread_t th = curr_thread; if (rb_thread_set_raised(th)) return EXIT_FAILURE; switch (ex & TAG_MASK) { case 0: status = EXIT_SUCCESS; break; case TAG_RETURN: error_pos(); warn_print(": unexpected return\n"); break; case TAG_NEXT: error_pos(); warn_print(": unexpected next\n"); break; case TAG_BREAK: error_pos(); warn_print(": unexpected break\n"); break; case TAG_REDO: error_pos(); warn_print(": unexpected redo\n"); break; case TAG_RETRY: error_pos(); warn_print(": retry outside of rescue clause\n"); break; case TAG_THROW: if (prot_tag && prot_tag->frame && prot_tag->frame->node) { NODE *tag = prot_tag->frame->node; warn_printf("%s:%d: uncaught throw\n", tag->nd_file, nd_line(tag)); } else { error_pos(); warn_printf(": unexpected throw\n"); } break; case TAG_RAISE: case TAG_FATAL: if (rb_obj_is_kind_of(ruby_errinfo, rb_eSystemExit)) { status = sysexit_status(ruby_errinfo); } else if (rb_obj_is_instance_of(ruby_errinfo, rb_eSignal)) { /* no message when exiting by signal */ } else { error_print(); } break; default: rb_bug("Unknown longjmp status %d", ex); break; } rb_thread_reset_raised(th); return status; } void ruby_options(argc, argv) int argc; char **argv; { int state; Init_stack((void*)&state); PUSH_TAG(PROT_NONE); if ((state = EXEC_TAG()) == 0) { ruby_process_options(argc, argv); } else { trace_func = 0; tracing = 0; exit(error_handle(state)); } POP_TAG(); } void rb_exec_end_proc _((void)); static void ruby_finalize_0() { PUSH_TAG(PROT_NONE); if (EXEC_TAG() == 0) { rb_trap_exit(); } POP_TAG(); rb_exec_end_proc(); } static void ruby_finalize_1() { signal(SIGINT, SIG_DFL); ruby_errinfo = 0; rb_gc_call_finalizer_at_exit(); trace_func = 0; tracing = 0; } void ruby_finalize() { ruby_finalize_0(); ruby_finalize_1(); } int ruby_cleanup(ex) int ex; { int state; volatile VALUE errs[2]; int nerr; errs[1] = ruby_errinfo; ruby_safe_level = 0; Init_stack((void *)&state); ruby_finalize_0(); errs[0] = ruby_errinfo; PUSH_TAG(PROT_NONE); PUSH_ITER(ITER_NOT); if ((state = EXEC_TAG()) == 0) { rb_thread_cleanup(); rb_thread_wait_other_threads(); } else if (ex == 0) { ex = state; } POP_ITER(); ruby_errinfo = errs[1]; ex = error_handle(ex); ruby_finalize_1(); POP_TAG(); for (nerr = 0; nerr < sizeof(errs) / sizeof(errs[0]); ++nerr) { VALUE err = errs[nerr]; if (!RTEST(err)) continue; if (rb_obj_is_kind_of(err, rb_eSystemExit)) { return sysexit_status(err); } else if (rb_obj_is_kind_of(err, rb_eSignal)) { VALUE sig = rb_iv_get(err, "signo"); ruby_default_signal(NUM2INT(sig)); } else if (ex == 0) { ex = 1; } } #if EXIT_SUCCESS != 0 || EXIT_FAILURE != 1 switch (ex) { #if EXIT_SUCCESS != 0 case 0: return EXIT_SUCCESS; #endif #if EXIT_FAILURE != 1 case 1: return EXIT_FAILURE; #endif } #endif return ex; } static int ruby_exec_internal() { int state; PUSH_TAG(PROT_NONE); PUSH_ITER(ITER_NOT); /* default visibility is private at toplevel */ SCOPE_SET(SCOPE_PRIVATE); if ((state = EXEC_TAG()) == 0) { eval_node(ruby_top_self, ruby_eval_tree); } POP_ITER(); POP_TAG(); return state; } void ruby_stop(ex) int ex; { exit(ruby_cleanup(ex)); } int ruby_exec() { volatile NODE *tmp; Init_stack((void*)&tmp); return ruby_exec_internal(); } void ruby_run() { int state; static int ex; if (ruby_nerrs > 0) exit(EXIT_FAILURE); state = ruby_exec(); if (state && !ex) ex = state; ruby_stop(ex); } static void compile_error(at) const char *at; { VALUE str; ruby_nerrs = 0; str = rb_str_buf_new2("compile error"); if (at) { rb_str_buf_cat2(str, " in "); rb_str_buf_cat2(str, at); } rb_str_buf_cat(str, "\n", 1); if (!NIL_P(ruby_errinfo)) { rb_str_append(str, rb_obj_as_string(ruby_errinfo)); } rb_exc_raise(rb_exc_new3(rb_eSyntaxError, str)); } VALUE rb_eval_string(str) const char *str; { VALUE v; NODE *oldsrc = ruby_current_node; ruby_current_node = 0; ruby_sourcefile = rb_source_filename("(eval)"); v = eval(ruby_top_self, rb_str_new2(str), Qnil, 0, 0); ruby_current_node = oldsrc; return v; } VALUE rb_eval_string_protect(str, state) const char *str; int *state; { return rb_protect((VALUE (*)_((VALUE)))rb_eval_string, (VALUE)str, state); } VALUE rb_eval_string_wrap(str, state) const char *str; int *state; { int status; VALUE self = ruby_top_self; VALUE wrapper = ruby_wrapper; VALUE val; PUSH_CLASS(ruby_wrapper = rb_module_new()); ruby_top_self = rb_obj_clone(ruby_top_self); rb_extend_object(ruby_top_self, ruby_wrapper); PUSH_FRAME(); ruby_frame->last_func = 0; ruby_frame->last_class = 0; ruby_frame->self = self; PUSH_CREF(ruby_wrapper); PUSH_SCOPE(); val = rb_eval_string_protect(str, &status); ruby_top_self = self; POP_SCOPE(); POP_FRAME(); POP_CLASS(); ruby_wrapper = wrapper; if (state) { *state = status; } else if (status) { JUMP_TAG(status); } return val; } NORETURN(static void localjump_error(const char*, VALUE, int)); static void localjump_error(mesg, value, reason) const char *mesg; VALUE value; int reason; { VALUE exc = rb_exc_new2(rb_eLocalJumpError, mesg); ID id; rb_iv_set(exc, "@exit_value", value); switch (reason) { case TAG_BREAK: id = rb_intern("break"); break; case TAG_REDO: id = rb_intern("redo"); break; case TAG_RETRY: id = rb_intern("retry"); break; case TAG_NEXT: id = rb_intern("next"); break; case TAG_RETURN: id = rb_intern("return"); break; default: id = rb_intern("noreason"); break; } rb_iv_set(exc, "@reason", ID2SYM(id)); rb_exc_raise(exc); } /* * call_seq: * local_jump_error.exit_value => obj * * Returns the exit value associated with this +LocalJumpError+. */ static VALUE localjump_xvalue(exc) VALUE exc; { return rb_iv_get(exc, "@exit_value"); } /* * call-seq: * local_jump_error.reason => symbol * * The reason this block was terminated: * :break, :redo, :retry, :next, :return, or :noreason. */ static VALUE localjump_reason(exc) VALUE exc; { return rb_iv_get(exc, "@reason"); } NORETURN(static void jump_tag_but_local_jump _((int,VALUE))); static void jump_tag_but_local_jump(state, val) int state; VALUE val; { if (val == Qundef) val = prot_tag->retval; switch (state) { case 0: break; case TAG_RETURN: localjump_error("unexpected return", val, state); break; case TAG_BREAK: localjump_error("unexpected break", val, state); break; case TAG_NEXT: localjump_error("unexpected next", val, state); break; case TAG_REDO: localjump_error("unexpected redo", Qnil, state); break; case TAG_RETRY: localjump_error("retry outside of rescue clause", Qnil, state); break; default: break; } JUMP_TAG(state); } VALUE rb_eval_cmd(cmd, arg, level) VALUE cmd, arg; int level; { int state; VALUE val = Qnil; /* OK */ struct SCOPE *saved_scope; volatile int safe = ruby_safe_level; if (OBJ_TAINTED(cmd)) { level = 4; } if (TYPE(cmd) != T_STRING) { PUSH_ITER(ITER_NOT); PUSH_TAG(PROT_NONE); ruby_safe_level = level; if ((state = EXEC_TAG()) == 0) { val = rb_funcall2(cmd, rb_intern("call"), RARRAY(arg)->len, RARRAY(arg)->ptr); } ruby_safe_level = safe; POP_TAG(); POP_ITER(); if (state) JUMP_TAG(state); return val; } saved_scope = ruby_scope; ruby_scope = top_scope; PUSH_FRAME(); ruby_frame->last_func = 0; ruby_frame->last_class = 0; ruby_frame->self = ruby_top_self; PUSH_CREF(ruby_wrapper ? ruby_wrapper : rb_cObject); ruby_safe_level = level; PUSH_TAG(PROT_NONE); if ((state = EXEC_TAG()) == 0) { val = eval(ruby_top_self, cmd, Qnil, 0, 0); } if (ruby_scope->flags & SCOPE_DONT_RECYCLE) scope_dup(saved_scope); ruby_scope = saved_scope; ruby_safe_level = safe; POP_TAG(); POP_FRAME(); if (state) jump_tag_but_local_jump(state, val); return val; } #define ruby_cbase (ruby_cref->nd_clss) static VALUE ev_const_defined(cref, id, self) NODE *cref; ID id; VALUE self; { NODE *cbase = cref; VALUE result; while (cbase && cbase->nd_next) { struct RClass *klass = RCLASS(cbase->nd_clss); if (!NIL_P(klass)) { if (klass->iv_tbl && st_lookup(klass->iv_tbl, id, &result)) { if (result == Qundef && NIL_P(rb_autoload_p((VALUE)klass, id))) { return Qfalse; } return Qtrue; } } cbase = cbase->nd_next; } return rb_const_defined(cref->nd_clss, id); } static VALUE ev_const_get(cref, id, self) NODE *cref; ID id; VALUE self; { NODE *cbase = cref; VALUE result; while (cbase && cbase->nd_next) { VALUE klass = cbase->nd_clss; if (!NIL_P(klass)) { while (RCLASS(klass)->iv_tbl && st_lookup(RCLASS(klass)->iv_tbl, id, &result)) { if (result == Qundef) { if (!RTEST(rb_autoload_load(klass, id))) break; continue; } return result; } } cbase = cbase->nd_next; } return rb_const_get(NIL_P(cref->nd_clss) ? CLASS_OF(self): cref->nd_clss, id); } static VALUE cvar_cbase() { NODE *cref = ruby_cref; while (cref && cref->nd_next && (NIL_P(cref->nd_clss) || FL_TEST(cref->nd_clss, FL_SINGLETON))) { cref = cref->nd_next; if (!cref->nd_next) { rb_warn("class variable access from toplevel singleton method"); } } if (NIL_P(cref->nd_clss)) { rb_raise(rb_eTypeError, "no class variables available"); } return cref->nd_clss; } /* * call-seq: * Module.nesting => array * * Returns the list of +Modules+ nested at the point of call. * * module M1 * module M2 * $a = Module.nesting * end * end * $a #=> [M1::M2, M1] * $a[0].name #=> "M1::M2" */ static VALUE rb_mod_nesting() { NODE *cbase = ruby_cref; VALUE ary = rb_ary_new(); while (cbase && cbase->nd_next) { if (!NIL_P(cbase->nd_clss)) rb_ary_push(ary, cbase->nd_clss); cbase = cbase->nd_next; } if (ruby_wrapper && RARRAY(ary)->len == 0) { rb_ary_push(ary, ruby_wrapper); } return ary; } /* * call-seq: * Module.constants => array * * Returns an array of the names of all constants defined in the * system. This list includes the names of all modules and classes. * * p Module.constants.sort[1..5] * * produces: * * ["ARGV", "ArgumentError", "Array", "Bignum", "Binding"] */ static VALUE rb_mod_s_constants() { NODE *cbase = ruby_cref; void *data = 0; while (cbase) { if (!NIL_P(cbase->nd_clss)) { data = rb_mod_const_at(cbase->nd_clss, data); } cbase = cbase->nd_next; } if (!NIL_P(ruby_cbase)) { data = rb_mod_const_of(ruby_cbase, data); } return rb_const_list(data); } void rb_frozen_class_p(klass) VALUE klass; { char *desc = "something(?!)"; if (OBJ_FROZEN(klass)) { if (FL_TEST(klass, FL_SINGLETON)) desc = "object"; else { switch (TYPE(klass)) { case T_MODULE: case T_ICLASS: desc = "module"; break; case T_CLASS: desc = "class"; break; } } rb_error_frozen(desc); } } void rb_undef(klass, id) VALUE klass; ID id; { VALUE origin; NODE *body; if (ruby_cbase == rb_cObject && klass == rb_cObject) { rb_secure(4); } if (ruby_safe_level >= 4 && !OBJ_TAINTED(klass)) { rb_raise(rb_eSecurityError, "Insecure: can't undef `%s'", rb_id2name(id)); } rb_frozen_class_p(klass); if (id == __id__ || id == __send__ || id == init) { rb_warn("undefining `%s' may cause serious problem", rb_id2name(id)); } body = search_method(klass, id, &origin); if (!body || !body->nd_body) { char *s0 = " class"; VALUE c = klass; if (FL_TEST(c, FL_SINGLETON)) { VALUE obj = rb_iv_get(klass, "__attached__"); switch (TYPE(obj)) { case T_MODULE: case T_CLASS: c = obj; s0 = ""; } } else if (TYPE(c) == T_MODULE) { s0 = " module"; } rb_name_error(id, "undefined method `%s' for%s `%s'", rb_id2name(id),s0,rb_class2name(c)); } rb_add_method(klass, id, 0, NOEX_PUBLIC); if (FL_TEST(klass, FL_SINGLETON)) { rb_funcall(rb_iv_get(klass, "__attached__"), singleton_undefined, 1, ID2SYM(id)); } else { rb_funcall(klass, undefined, 1, ID2SYM(id)); } } /* * call-seq: * undef_method(symbol) => self * * Prevents the current class from responding to calls to the named * method. Contrast this with remove_method, which deletes * the method from the particular class; Ruby will still search * superclasses and mixed-in modules for a possible receiver. * * class Parent * def hello * puts "In parent" * end * end * class Child < Parent * def hello * puts "In child" * end * end * * * c = Child.new * c.hello * * * class Child * remove_method :hello # remove from child, still in parent * end * c.hello * * * class Child * undef_method :hello # prevent any calls to 'hello' * end * c.hello * * produces: * * In child * In parent * prog.rb:23: undefined method `hello' for # (NoMethodError) */ static VALUE rb_mod_undef_method(argc, argv, mod) int argc; VALUE *argv; VALUE mod; { int i; for (i=0; ind_body) { if (TYPE(klass) == T_MODULE) { orig = search_method(rb_cObject, def, &origin); } } if (!orig || !orig->nd_body) { print_undef(klass, def); } if (FL_TEST(klass, FL_SINGLETON)) { singleton = rb_iv_get(klass, "__attached__"); } body = orig->nd_body; orig->nd_cnt++; if (nd_type(body) == NODE_FBODY) { /* was alias */ def = body->nd_mid; origin = body->nd_orig; body = body->nd_head; } rb_clear_cache_by_id(name); if (RTEST(ruby_verbose) && st_lookup(RCLASS(klass)->m_tbl, name, &data)) { node = (NODE *)data; if (node->nd_cnt == 0 && node->nd_body) { rb_warning("discarding old %s", rb_id2name(name)); } } st_insert(RCLASS(klass)->m_tbl, name, (st_data_t)NEW_METHOD(NEW_FBODY(body, def, origin), NOEX_WITH_SAFE(orig->nd_noex))); if (!ruby_running) return; if (singleton) { rb_funcall(singleton, singleton_added, 1, ID2SYM(name)); } else { rb_funcall(klass, added, 1, ID2SYM(name)); } } /* * call-seq: * alias_method(new_name, old_name) => self * * Makes new_name a new copy of the method old_name. This can * be used to retain access to methods that are overridden. * * module Mod * alias_method :orig_exit, :exit * def exit(code=0) * puts "Exiting with code #{code}" * orig_exit(code) * end * end * include Mod * exit(99) * * produces: * * Exiting with code 99 */ static VALUE rb_mod_alias_method(mod, newname, oldname) VALUE mod, newname, oldname; { rb_alias(mod, rb_to_id(newname), rb_to_id(oldname)); return mod; } NODE * rb_copy_node_scope(node, rval) NODE *node; NODE *rval; { NODE *copy = NEW_NODE(NODE_SCOPE,0,rval,node->nd_next); if (node->nd_tbl) { copy->nd_tbl = ALLOC_N(ID, node->nd_tbl[0]+1); MEMCPY(copy->nd_tbl, node->nd_tbl, ID, node->nd_tbl[0]+1); } else { copy->nd_tbl = 0; } return copy; } #ifdef C_ALLOCA # define TMP_PROTECT NODE * volatile tmp__protect_tmp=0 # define TMP_ALLOC(n) \ (tmp__protect_tmp = NEW_NODE(NODE_ALLOCA, \ ALLOC_N(VALUE,n),tmp__protect_tmp,n), \ (void*)tmp__protect_tmp->nd_head) #else # define TMP_PROTECT typedef int foobazzz # define TMP_ALLOC(n) ALLOCA_N(VALUE,n) #endif #define SETUP_ARGS0(anode,extra) do {\ NODE *n = anode;\ if (!n) {\ argc = 0;\ argv = 0;\ }\ else if (nd_type(n) == NODE_ARRAY) {\ argc=anode->nd_alen;\ if (argc > 0) {\ int i;\ n = anode;\ argv = TMP_ALLOC(argc+extra);\ for (i=0;ind_head);\ n=n->nd_next;\ }\ }\ else {\ argc = 0;\ argv = 0;\ }\ }\ else {\ VALUE args = rb_eval(self,n);\ if (TYPE(args) != T_ARRAY)\ args = rb_ary_to_ary(args);\ argc = RARRAY(args)->len;\ argv = TMP_ALLOC(argc+extra);\ MEMCPY(argv, RARRAY(args)->ptr, VALUE, argc);\ }\ } while (0) #define SETUP_ARGS(anode) SETUP_ARGS0(anode,0) #define BEGIN_CALLARGS do {\ struct BLOCK *tmp_block = ruby_block;\ int tmp_iter = ruby_iter->iter;\ switch (tmp_iter) {\ case ITER_PRE:\ if (ruby_block) ruby_block = ruby_block->outer;\ case ITER_PAS:\ tmp_iter = ITER_NOT;\ }\ PUSH_ITER(tmp_iter) #define END_CALLARGS \ ruby_block = tmp_block;\ POP_ITER();\ } while (0) #define MATCH_DATA *rb_svar(node->nd_cnt) static char* is_defined _((VALUE, NODE*, char*)); static char* arg_defined(self, node, buf, type) VALUE self; NODE *node; char *buf; char *type; { int argc; int i; if (!node) return type; /* no args */ if (nd_type(node) == NODE_ARRAY) { argc=node->nd_alen; if (argc > 0) { for (i=0;ind_head, buf)) return 0; node = node->nd_next; } } } else if (!is_defined(self, node, buf)) { return 0; } return type; } static char* is_defined(self, node, buf) VALUE self; NODE *node; /* OK */ char *buf; { VALUE val; /* OK */ int state; again: if (!node) return "expression"; switch (nd_type(node)) { case NODE_SUPER: case NODE_ZSUPER: if (ruby_frame->last_func == 0) return 0; else if (ruby_frame->last_class == 0) return 0; val = ruby_frame->last_class; if (rb_method_boundp(RCLASS(val)->super, ruby_frame->orig_func, 0)) { if (nd_type(node) == NODE_SUPER) { return arg_defined(self, node->nd_args, buf, "super"); } return "super"; } break; case NODE_VCALL: case NODE_FCALL: val = self; goto check_bound; case NODE_ATTRASGN: val = self; if (node->nd_recv == (NODE *)1) goto check_bound; case NODE_CALL: PUSH_TAG(PROT_NONE); if ((state = EXEC_TAG()) == 0) { val = rb_eval(self, node->nd_recv); } POP_TAG(); if (state) { ruby_errinfo = Qnil; return 0; } check_bound: { int call = nd_type(node)==NODE_CALL; val = CLASS_OF(val); if (call) { int noex; ID id = node->nd_mid; if (!rb_get_method_body(&val, &id, &noex)) break; if ((noex & NOEX_PRIVATE)) break; if ((noex & NOEX_PROTECTED) && !rb_obj_is_kind_of(self, rb_class_real(val))) break; } else if (!rb_method_boundp(val, node->nd_mid, call)) break; return arg_defined(self, node->nd_args, buf, nd_type(node) == NODE_ATTRASGN ? "assignment" : "method"); } break; case NODE_MATCH2: case NODE_MATCH3: return "method"; case NODE_YIELD: if (rb_block_given_p()) { return "yield"; } break; case NODE_SELF: return "self"; case NODE_NIL: return "nil"; case NODE_TRUE: return "true"; case NODE_FALSE: return "false"; case NODE_ATTRSET: case NODE_OP_ASGN1: case NODE_OP_ASGN2: case NODE_OP_ASGN_OR: case NODE_OP_ASGN_AND: case NODE_MASGN: case NODE_LASGN: case NODE_DASGN: case NODE_DASGN_CURR: case NODE_GASGN: case NODE_IASGN: case NODE_CDECL: case NODE_CVDECL: case NODE_CVASGN: return "assignment"; case NODE_LVAR: return "local-variable"; case NODE_DVAR: return "local-variable(in-block)"; case NODE_GVAR: if (rb_gvar_defined(node->nd_entry)) { return "global-variable"; } break; case NODE_IVAR: if (rb_ivar_defined(self, node->nd_vid)) { return "instance-variable"; } break; case NODE_CONST: if (ev_const_defined(ruby_cref, node->nd_vid, self)) { return "constant"; } break; case NODE_CVAR: if (rb_cvar_defined(cvar_cbase(), node->nd_vid)) { return "class variable"; } break; case NODE_COLON2: PUSH_TAG(PROT_NONE); if ((state = EXEC_TAG()) == 0) { val = rb_eval(self, node->nd_head); } POP_TAG(); if (state) { ruby_errinfo = Qnil; return 0; } else { switch (TYPE(val)) { case T_CLASS: case T_MODULE: if (rb_const_defined_from(val, node->nd_mid)) return "constant"; break; default: if (rb_method_boundp(CLASS_OF(val), node->nd_mid, 1)) { return "method"; } } } break; case NODE_COLON3: if (rb_const_defined_from(rb_cObject, node->nd_mid)) { return "constant"; } break; case NODE_NTH_REF: if (RTEST(rb_reg_nth_defined(node->nd_nth, MATCH_DATA))) { sprintf(buf, "$%d", (int)node->nd_nth); return buf; } break; case NODE_BACK_REF: if (RTEST(rb_reg_nth_defined(0, MATCH_DATA))) { sprintf(buf, "$%c", (char)node->nd_nth); return buf; } break; case NODE_NEWLINE: node = node->nd_next; goto again; default: PUSH_TAG(PROT_NONE); if ((state = EXEC_TAG()) == 0) { rb_eval(self, node); } POP_TAG(); if (!state) { return "expression"; } ruby_errinfo = Qnil; break; } return 0; } static int handle_rescue _((VALUE,NODE*)); static void blk_free(); static VALUE rb_obj_is_proc(proc) VALUE proc; { if (TYPE(proc) == T_DATA && RDATA(proc)->dfree == (RUBY_DATA_FUNC)blk_free) { return Qtrue; } return Qfalse; } void rb_add_event_hook(func, events) rb_event_hook_func_t func; rb_event_t events; { rb_event_hook_t *hook; hook = ALLOC(rb_event_hook_t); hook->func = func; hook->events = events; hook->next = event_hooks; event_hooks = hook; } int rb_remove_event_hook(func) rb_event_hook_func_t func; { rb_event_hook_t *prev, *hook; prev = NULL; hook = event_hooks; while (hook) { if (hook->func == func) { if (prev) { prev->next = hook->next; } else { event_hooks = hook->next; } xfree(hook); return 0; } prev = hook; hook = hook->next; } return -1; } /* * call-seq: * set_trace_func(proc) => proc * set_trace_func(nil) => nil * * Establishes _proc_ as the handler for tracing, or disables * tracing if the parameter is +nil+. _proc_ takes up * to six parameters: an event name, a filename, a line number, an * object id, a binding, and the name of a class. _proc_ is * invoked whenever an event occurs. Events are: c-call * (call a C-language routine), c-return (return from a * C-language routine), call (call a Ruby method), * class (start a class or module definition), * end (finish a class or module definition), * line (execute code on a new line), raise * (raise an exception), and return (return from a Ruby * method). Tracing is disabled within the context of _proc_. * * class Test * def test * a = 1 * b = 2 * end * end * * set_trace_func proc { |event, file, line, id, binding, classname| * printf "%8s %s:%-2d %10s %8s\n", event, file, line, id, classname * } * t = Test.new * t.test * * line prog.rb:11 false * c-call prog.rb:11 new Class * c-call prog.rb:11 initialize Object * c-return prog.rb:11 initialize Object * c-return prog.rb:11 new Class * line prog.rb:12 false * call prog.rb:2 test Test * line prog.rb:3 test Test * line prog.rb:4 test Test * return prog.rb:4 test Test */ static VALUE set_trace_func(obj, trace) VALUE obj, trace; { rb_event_hook_t *hook; rb_secure(4); if (NIL_P(trace)) { trace_func = 0; rb_remove_event_hook(call_trace_func); return Qnil; } if (!rb_obj_is_proc(trace)) { rb_raise(rb_eTypeError, "trace_func needs to be Proc"); } trace_func = trace; for (hook = event_hooks; hook; hook = hook->next) { if (hook->func == call_trace_func) return trace; } rb_add_event_hook(call_trace_func, RUBY_EVENT_ALL); return trace; } static char * get_event_name(rb_event_t event) { switch (event) { case RUBY_EVENT_LINE: return "line"; case RUBY_EVENT_CLASS: return "class"; case RUBY_EVENT_END: return "end"; case RUBY_EVENT_CALL: return "call"; case RUBY_EVENT_RETURN: return "return"; case RUBY_EVENT_C_CALL: return "c-call"; case RUBY_EVENT_C_RETURN: return "c-return"; case RUBY_EVENT_RAISE: return "raise"; default: return "unknown"; } } static void call_trace_func(event, node, self, id, klass) rb_event_t event; NODE *node; VALUE self; ID id; VALUE klass; /* OK */ { int state, raised; struct FRAME *prev; NODE *node_save; VALUE srcfile; char *event_name; rb_thread_t th = curr_thread; if (!trace_func) return; if (tracing) return; if (ruby_in_compile) return; if (id == ID_ALLOCATOR) return; if (!(node_save = ruby_current_node)) { node_save = NEW_NEWLINE(0); } tracing = 1; prev = ruby_frame; PUSH_FRAME(); *ruby_frame = *prev; ruby_frame->prev = prev; ruby_frame->iter = 0; /* blocks not available anyway */ if (node) { ruby_current_node = node; ruby_frame->node = node; ruby_sourcefile = node->nd_file; ruby_sourceline = nd_line(node); } if (klass) { if (TYPE(klass) == T_ICLASS) { klass = RBASIC(klass)->klass; } else if (FL_TEST(klass, FL_SINGLETON)) { klass = rb_iv_get(klass, "__attached__"); } } PUSH_TAG(PROT_NONE); raised = rb_thread_reset_raised(th); if ((state = EXEC_TAG()) == 0) { srcfile = rb_str_new2(ruby_sourcefile?ruby_sourcefile:"(ruby)"); event_name = get_event_name(event); proc_invoke(trace_func, rb_ary_new3(6, rb_str_new2(event_name), srcfile, INT2FIX(ruby_sourceline), id?ID2SYM(id):Qnil, self?rb_f_binding(self):Qnil, klass), Qundef, 0); } if (raised) rb_thread_set_raised(th); POP_TAG(); POP_FRAME(); tracing = 0; ruby_current_node = node_save; SET_CURRENT_SOURCE(); if (state) JUMP_TAG(state); } static VALUE avalue_to_svalue(v) VALUE v; { VALUE tmp, top; tmp = rb_check_array_type(v); if (NIL_P(tmp)) { return v; } if (RARRAY(tmp)->len == 0) { return Qundef; } if (RARRAY(tmp)->len == 1) { top = rb_check_array_type(RARRAY(tmp)->ptr[0]); if (NIL_P(top)) { return RARRAY(tmp)->ptr[0]; } if (RARRAY(top)->len > 1) { return v; } return top; } return tmp; } static VALUE svalue_to_avalue(v) VALUE v; { VALUE tmp, top; if (v == Qundef) return rb_ary_new2(0); tmp = rb_check_array_type(v); if (NIL_P(tmp)) { return rb_ary_new3(1, v); } if (RARRAY(tmp)->len == 1) { top = rb_check_array_type(RARRAY(tmp)->ptr[0]); if (!NIL_P(top) && RARRAY(top)->len > 1) { return tmp; } return rb_ary_new3(1, v); } return tmp; } static VALUE svalue_to_mrhs(v, lhs) VALUE v; NODE *lhs; { VALUE tmp; if (v == Qundef) return rb_ary_new2(0); tmp = rb_check_array_type(v); if (NIL_P(tmp)) { return rb_ary_new3(1, v); } /* no lhs means splat lhs only */ if (!lhs) { return rb_ary_new3(1, v); } return tmp; } static VALUE avalue_splat(v) VALUE v; { if (RARRAY(v)->len == 0) { return Qundef; } if (RARRAY(v)->len == 1) { return RARRAY(v)->ptr[0]; } return v; } #if 1 VALUE rb_Array(val) VALUE val; { VALUE tmp = rb_check_array_type(val); if (NIL_P(tmp)) { /* hack to avoid invoke Object#to_a */ VALUE origin; ID id = rb_intern("to_a"); if (search_method(CLASS_OF(val), id, &origin) && RCLASS(origin)->m_tbl != RCLASS(rb_mKernel)->m_tbl) { /* exclude Kernel#to_a */ val = rb_funcall(val, id, 0); if (TYPE(val) != T_ARRAY) { rb_raise(rb_eTypeError, "`to_a' did not return Array"); } return val; } else { return rb_ary_new3(1, val); } } return tmp; } #endif static VALUE splat_value(v) VALUE v; { if (NIL_P(v)) return rb_ary_new3(1, Qnil); return rb_Array(v); } static VALUE class_prefix(self, cpath) VALUE self; NODE *cpath; { if (!cpath) { rb_bug("class path missing"); } if (cpath->nd_head) { VALUE c = rb_eval(self, cpath->nd_head); switch (TYPE(c)) { case T_CLASS: case T_MODULE: break; default: rb_raise(rb_eTypeError, "%s is not a class/module", RSTRING(rb_obj_as_string(c))->ptr); } return c; } else if (nd_type(cpath) == NODE_COLON2) { return ruby_cbase; } else if (ruby_wrapper) { return ruby_wrapper; } else { return rb_cObject; } } #define return_value(v) do {\ if ((prot_tag->retval = (v)) == Qundef) {\ prot_tag->retval = Qnil;\ }\ } while (0) NORETURN(static void return_jump _((VALUE))); NORETURN(static void break_jump _((VALUE))); NORETURN(static void next_jump _((VALUE))); NORETURN(static void unknown_node _((NODE * volatile))); static void unknown_node(node) NODE *volatile node; { ruby_current_node = 0; if (node->flags == 0) { rb_bug("terminated node (0x%lx)", node); } else if (BUILTIN_TYPE(node) != T_NODE) { rb_bug("not a node 0x%02lx (0x%lx)", BUILTIN_TYPE(node), node); } else { rb_bug("unknown node type %d (0x%lx)", nd_type(node), node); } } static VALUE rb_eval(self, n) VALUE self; NODE *n; { NODE * volatile contnode = 0; NODE * volatile node = n; int state; volatile VALUE result = Qnil; st_data_t data; #define RETURN(v) do { \ result = (v); \ goto finish; \ } while (0) again: if (!node) RETURN(Qnil); ruby_current_node = node; switch (nd_type(node)) { case NODE_BLOCK: if (contnode) { result = rb_eval(self, node); break; } contnode = node->nd_next; node = node->nd_head; goto again; case NODE_POSTEXE: rb_f_END(); nd_set_type(node, NODE_NIL); /* exec just once */ result = Qnil; break; /* begin .. end without clauses */ case NODE_BEGIN: node = node->nd_body; goto again; /* nodes for speed-up(default match) */ case NODE_MATCH: result = rb_reg_match2(node->nd_lit); break; /* nodes for speed-up(literal match) */ case NODE_MATCH2: { VALUE l = rb_eval(self,node->nd_recv); VALUE r = rb_eval(self,node->nd_value); result = rb_reg_match(l, r); } break; /* nodes for speed-up(literal match) */ case NODE_MATCH3: { VALUE r = rb_eval(self,node->nd_recv); VALUE l = rb_eval(self,node->nd_value); if (TYPE(l) == T_STRING) { result = rb_reg_match(r, l); } else { result = rb_funcall(l, match, 1, r); } } break; /* node for speed-up(top-level loop for -n/-p) */ case NODE_OPT_N: PUSH_TAG(PROT_LOOP); switch (state = EXEC_TAG()) { case 0: opt_n_next: while (!NIL_P(rb_gets())) { opt_n_redo: rb_eval(self, node->nd_body); } break; case TAG_REDO: state = 0; goto opt_n_redo; case TAG_NEXT: state = 0; goto opt_n_next; case TAG_BREAK: state = 0; default: break; } POP_TAG(); if (state) JUMP_TAG(state); RETURN(Qnil); case NODE_SELF: RETURN(self); case NODE_NIL: RETURN(Qnil); case NODE_TRUE: RETURN(Qtrue); case NODE_FALSE: RETURN(Qfalse); case NODE_IF: EXEC_EVENT_HOOK(RUBY_EVENT_LINE, node, self, ruby_frame->last_func, ruby_frame->last_class); if (RTEST(rb_eval(self, node->nd_cond))) { node = node->nd_body; } else { node = node->nd_else; } goto again; case NODE_WHEN: while (node) { NODE *tag; if (nd_type(node) != NODE_WHEN) goto again; tag = node->nd_head; while (tag) { EXEC_EVENT_HOOK(RUBY_EVENT_LINE, tag, self, ruby_frame->last_func, ruby_frame->last_class); if (tag->nd_head && nd_type(tag->nd_head) == NODE_WHEN) { VALUE v = rb_eval(self, tag->nd_head->nd_head); long i; if (TYPE(v) != T_ARRAY) v = rb_ary_to_ary(v); for (i=0; ilen; i++) { if (RTEST(RARRAY(v)->ptr[i])) { node = node->nd_body; goto again; } } tag = tag->nd_next; continue; } if (RTEST(rb_eval(self, tag->nd_head))) { node = node->nd_body; goto again; } tag = tag->nd_next; } node = node->nd_next; } RETURN(Qnil); case NODE_CASE: { VALUE val; val = rb_eval(self, node->nd_head); node = node->nd_body; while (node) { NODE *tag; if (nd_type(node) != NODE_WHEN) { goto again; } tag = node->nd_head; while (tag) { EXEC_EVENT_HOOK(RUBY_EVENT_LINE, tag, self, ruby_frame->last_func, ruby_frame->last_class); if (tag->nd_head && nd_type(tag->nd_head) == NODE_WHEN) { VALUE v = rb_eval(self, tag->nd_head->nd_head); long i; if (TYPE(v) != T_ARRAY) v = rb_ary_to_ary(v); for (i=0; ilen; i++) { if (RTEST(rb_funcall2(RARRAY(v)->ptr[i], eqq, 1, &val))){ node = node->nd_body; goto again; } } tag = tag->nd_next; continue; } if (RTEST(rb_funcall2(rb_eval(self, tag->nd_head), eqq, 1, &val))) { node = node->nd_body; goto again; } tag = tag->nd_next; } node = node->nd_next; } } RETURN(Qnil); case NODE_WHILE: PUSH_TAG(PROT_LOOP); result = Qnil; switch (state = EXEC_TAG()) { case 0: if (node->nd_state && !RTEST(rb_eval(self, node->nd_cond))) goto while_out; do { while_redo: rb_eval(self, node->nd_body); while_next: ; } while (RTEST(rb_eval(self, node->nd_cond))); break; case TAG_REDO: state = 0; goto while_redo; case TAG_NEXT: state = 0; goto while_next; case TAG_BREAK: if (TAG_DST()) { state = 0; result = prot_tag->retval; } /* fall through */ default: break; } while_out: POP_TAG(); if (state) JUMP_TAG(state); RETURN(result); case NODE_UNTIL: PUSH_TAG(PROT_LOOP); result = Qnil; switch (state = EXEC_TAG()) { case 0: if (node->nd_state && RTEST(rb_eval(self, node->nd_cond))) goto until_out; do { until_redo: rb_eval(self, node->nd_body); until_next: ; } while (!RTEST(rb_eval(self, node->nd_cond))); break; case TAG_REDO: state = 0; goto until_redo; case TAG_NEXT: state = 0; goto until_next; case TAG_BREAK: if (TAG_DST()) { state = 0; result = prot_tag->retval; } /* fall through */ default: break; } until_out: POP_TAG(); if (state) JUMP_TAG(state); RETURN(result); case NODE_BLOCK_PASS: result = block_pass(self, node); break; case NODE_ITER: case NODE_FOR: { PUSH_TAG(PROT_LOOP); PUSH_BLOCK(node->nd_var, node->nd_body); state = EXEC_TAG(); if (state == 0) { iter_retry: PUSH_ITER(ITER_PRE); if (nd_type(node) == NODE_ITER) { result = rb_eval(self, node->nd_iter); } else { VALUE recv; _block.flags &= ~BLOCK_D_SCOPE; BEGIN_CALLARGS; recv = rb_eval(self, node->nd_iter); END_CALLARGS; ruby_current_node = node; SET_CURRENT_SOURCE(); result = rb_call(CLASS_OF(recv),recv,each,0,0,0,self); } POP_ITER(); } else if (state == TAG_BREAK && TAG_DST()) { result = prot_tag->retval; state = 0; } else if (state == TAG_RETRY) { state = 0; goto iter_retry; } POP_BLOCK(); POP_TAG(); switch (state) { case 0: break; default: JUMP_TAG(state); } } break; case NODE_BREAK: break_jump(rb_eval(self, node->nd_stts)); break; case NODE_NEXT: CHECK_INTS; next_jump(rb_eval(self, node->nd_stts)); break; case NODE_REDO: CHECK_INTS; JUMP_TAG(TAG_REDO); break; case NODE_RETRY: CHECK_INTS; JUMP_TAG(TAG_RETRY); break; case NODE_SPLAT: result = splat_value(rb_eval(self, node->nd_head)); break; case NODE_TO_ARY: result = rb_ary_to_ary(rb_eval(self, node->nd_head)); break; case NODE_SVALUE: result = avalue_splat(rb_eval(self, node->nd_head)); if (result == Qundef) result = Qnil; break; case NODE_YIELD: if (node->nd_head) { result = rb_eval(self, node->nd_head); ruby_current_node = node; } else { result = Qundef; /* no arg */ } SET_CURRENT_SOURCE(); result = rb_yield_0(result, 0, 0, 0, node->nd_state); break; case NODE_RESCUE: { volatile VALUE e_info = ruby_errinfo; volatile int rescuing = 0; PUSH_TAG(PROT_NONE); if ((state = EXEC_TAG()) == 0) { retry_entry: result = rb_eval(self, node->nd_head); } else if (rescuing) { if (rescuing < 0) { /* in rescue argument, just reraise */ } else if (state == TAG_RETRY) { rescuing = state = 0; ruby_errinfo = e_info; goto retry_entry; } else if (state != TAG_RAISE) { result = prot_tag->retval; } } else if (state == TAG_RAISE) { NODE *resq = node->nd_resq; rescuing = -1; while (resq) { ruby_current_node = resq; if (handle_rescue(self, resq)) { state = 0; rescuing = 1; result = rb_eval(self, resq->nd_body); break; } resq = resq->nd_head; /* next rescue */ } } else { result = prot_tag->retval; } POP_TAG(); if (state != TAG_RAISE) ruby_errinfo = e_info; if (state) { JUMP_TAG(state); } /* no exception raised */ if (!rescuing && (node = node->nd_else)) { /* else clause given */ goto again; } } break; case NODE_ENSURE: PUSH_TAG(PROT_NONE); if ((state = EXEC_TAG()) == 0) { result = rb_eval(self, node->nd_head); } POP_TAG(); if (node->nd_ensr && !thread_no_ensure()) { VALUE retval = prot_tag->retval; /* save retval */ VALUE errinfo = ruby_errinfo; rb_eval(self, node->nd_ensr); return_value(retval); ruby_errinfo = errinfo; } if (state) JUMP_TAG(state); break; case NODE_AND: result = rb_eval(self, node->nd_1st); if (!RTEST(result)) break; node = node->nd_2nd; goto again; case NODE_OR: result = rb_eval(self, node->nd_1st); if (RTEST(result)) break; node = node->nd_2nd; goto again; case NODE_NOT: if (RTEST(rb_eval(self, node->nd_body))) result = Qfalse; else result = Qtrue; break; case NODE_DOT2: case NODE_DOT3: { VALUE beg = rb_eval(self, node->nd_beg); VALUE end = rb_eval(self, node->nd_end); result = rb_range_new(beg, end, nd_type(node) == NODE_DOT3); } break; case NODE_FLIP2: /* like AWK */ { VALUE *flip = rb_svar(node->nd_cnt); if (!flip) rb_bug("unexpected local variable"); if (!RTEST(*flip)) { if (RTEST(rb_eval(self, node->nd_beg))) { *flip = RTEST(rb_eval(self, node->nd_end))?Qfalse:Qtrue; result = Qtrue; } else { result = Qfalse; } } else { if (RTEST(rb_eval(self, node->nd_end))) { *flip = Qfalse; } result = Qtrue; } } break; case NODE_FLIP3: /* like SED */ { VALUE *flip = rb_svar(node->nd_cnt); if (!flip) rb_bug("unexpected local variable"); if (!RTEST(*flip)) { result = RTEST(rb_eval(self, node->nd_beg)) ? Qtrue : Qfalse; *flip = result; } else { if (RTEST(rb_eval(self, node->nd_end))) { *flip = Qfalse; } result = Qtrue; } } break; case NODE_RETURN: return_jump(rb_eval(self, node->nd_stts)); break; case NODE_ARGSCAT: { VALUE args = rb_eval(self, node->nd_head); result = rb_ary_concat(args, splat_value(rb_eval(self, node->nd_body))); } break; case NODE_ARGSPUSH: { VALUE args = rb_ary_dup(rb_eval(self, node->nd_head)); result = rb_ary_push(args, rb_eval(self, node->nd_body)); } break; case NODE_ATTRASGN: { VALUE recv; int argc; VALUE *argv; /* used in SETUP_ARGS */ int scope; TMP_PROTECT; BEGIN_CALLARGS; if (node->nd_recv == (NODE *)1) { recv = self; scope = 1; } else { recv = rb_eval(self, node->nd_recv); scope = 0; } SETUP_ARGS(node->nd_args); END_CALLARGS; ruby_current_node = node; SET_CURRENT_SOURCE(); rb_call(CLASS_OF(recv),recv,node->nd_mid,argc,argv,scope,self); result = argv[argc-1]; } break; case NODE_CALL: { VALUE recv; int argc; VALUE *argv; /* used in SETUP_ARGS */ TMP_PROTECT; BEGIN_CALLARGS; recv = rb_eval(self, node->nd_recv); SETUP_ARGS(node->nd_args); END_CALLARGS; ruby_current_node = node; SET_CURRENT_SOURCE(); result = rb_call(CLASS_OF(recv),recv,node->nd_mid,argc,argv,0,self); } break; case NODE_FCALL: { int argc; VALUE *argv; /* used in SETUP_ARGS */ TMP_PROTECT; BEGIN_CALLARGS; SETUP_ARGS(node->nd_args); END_CALLARGS; ruby_current_node = node; SET_CURRENT_SOURCE(); result = rb_call(CLASS_OF(self),self,node->nd_mid,argc,argv,1,self); } break; case NODE_VCALL: SET_CURRENT_SOURCE(); result = rb_call(CLASS_OF(self),self,node->nd_mid,0,0,2,self); break; case NODE_SUPER: case NODE_ZSUPER: { int argc; VALUE *argv; /* used in SETUP_ARGS */ TMP_PROTECT; if (ruby_frame->last_class == 0) { if (ruby_frame->last_func) { rb_name_error(ruby_frame->last_func, "superclass method `%s' disabled", rb_id2name(ruby_frame->orig_func)); } else { rb_raise(rb_eNoMethodError, "super called outside of method"); } } if (nd_type(node) == NODE_ZSUPER) { argc = ruby_frame->argc; if (argc && DMETHOD_P()) { if (TYPE(RBASIC(ruby_scope)->klass) != T_ARRAY || RARRAY(RBASIC(ruby_scope)->klass)->len != argc) { rb_raise(rb_eRuntimeError, "super: specify arguments explicitly"); } argv = RARRAY(RBASIC(ruby_scope)->klass)->ptr; } else if (!ruby_scope->local_vars) { argc = 0; argv = 0; } else { argv = ruby_scope->local_vars + 2; } } else { BEGIN_CALLARGS; SETUP_ARGS(node->nd_args); END_CALLARGS; ruby_current_node = node; } SET_CURRENT_SOURCE(); result = rb_call_super(argc, argv); } break; case NODE_SCOPE: { struct FRAME frame; NODE *saved_cref = 0; frame = *ruby_frame; frame.tmp = ruby_frame; ruby_frame = &frame; PUSH_SCOPE(); PUSH_TAG(PROT_NONE); if (node->nd_rval) { saved_cref = ruby_cref; ruby_cref = (NODE*)node->nd_rval; } if (node->nd_tbl) { VALUE *vars = ALLOCA_N(VALUE, node->nd_tbl[0]+1); *vars++ = (VALUE)node; ruby_scope->local_vars = vars; rb_mem_clear(ruby_scope->local_vars, node->nd_tbl[0]); ruby_scope->local_tbl = node->nd_tbl; } else { ruby_scope->local_vars = 0; ruby_scope->local_tbl = 0; } if ((state = EXEC_TAG()) == 0) { result = rb_eval(self, node->nd_next); } POP_TAG(); POP_SCOPE(); ruby_frame = frame.tmp; if (saved_cref) ruby_cref = saved_cref; if (state) JUMP_TAG(state); } break; case NODE_OP_ASGN1: { int argc; VALUE *argv; /* used in SETUP_ARGS */ VALUE recv, val, tmp; NODE *rval; TMP_PROTECT; recv = rb_eval(self, node->nd_recv); rval = node->nd_args->nd_head; SETUP_ARGS0(node->nd_args->nd_body, 1); val = rb_funcall3(recv, aref, argc, argv); switch (node->nd_mid) { case 0: /* OR */ if (RTEST(val)) RETURN(val); val = rb_eval(self, rval); break; case 1: /* AND */ if (!RTEST(val)) RETURN(val); val = rb_eval(self, rval); break; default: tmp = rb_eval(self, rval); val = rb_funcall3(val, node->nd_mid, 1, &tmp); } argv[argc] = val; rb_funcall2(recv, aset, argc+1, argv); result = val; } break; case NODE_OP_ASGN2: { ID id = node->nd_next->nd_vid; VALUE recv, val, tmp; recv = rb_eval(self, node->nd_recv); val = rb_funcall3(recv, id, 0, 0); switch (node->nd_next->nd_mid) { case 0: /* OR */ if (RTEST(val)) RETURN(val); val = rb_eval(self, node->nd_value); break; case 1: /* AND */ if (!RTEST(val)) RETURN(val); val = rb_eval(self, node->nd_value); break; default: tmp = rb_eval(self, node->nd_value); val = rb_funcall3(val, node->nd_next->nd_mid, 1, &tmp); } rb_funcall2(recv, node->nd_next->nd_aid, 1, &val); result = val; } break; case NODE_OP_ASGN_AND: result = rb_eval(self, node->nd_head); if (!RTEST(result)) break; node = node->nd_value; goto again; case NODE_OP_ASGN_OR: if ((node->nd_aid && !is_defined(self, node->nd_head, 0)) || !RTEST(result = rb_eval(self, node->nd_head))) { node = node->nd_value; goto again; } break; case NODE_MASGN: result = massign(self, node, rb_eval(self, node->nd_value), 0); break; case NODE_LASGN: if (ruby_scope->local_vars == 0) rb_bug("unexpected local variable assignment"); result = rb_eval(self, node->nd_value); ruby_scope->local_vars[node->nd_cnt] = result; break; case NODE_DASGN: result = rb_eval(self, node->nd_value); dvar_asgn(node->nd_vid, result); break; case NODE_DASGN_CURR: result = rb_eval(self, node->nd_value); dvar_asgn_curr(node->nd_vid, result); break; case NODE_GASGN: result = rb_eval(self, node->nd_value); rb_gvar_set(node->nd_entry, result); break; case NODE_IASGN: result = rb_eval(self, node->nd_value); rb_ivar_set(self, node->nd_vid, result); break; case NODE_CDECL: result = rb_eval(self, node->nd_value); if (node->nd_vid == 0) { rb_const_set(class_prefix(self, node->nd_else), node->nd_else->nd_mid, result); } else { rb_const_set(ruby_cbase, node->nd_vid, result); } break; case NODE_CVDECL: if (NIL_P(ruby_cbase)) { rb_raise(rb_eTypeError, "no class/module to define class variable"); } result = rb_eval(self, node->nd_value); rb_cvar_set(cvar_cbase(), node->nd_vid, result, Qtrue); break; case NODE_CVASGN: result = rb_eval(self, node->nd_value); rb_cvar_set(cvar_cbase(), node->nd_vid, result, Qfalse); break; case NODE_LVAR: if (ruby_scope->local_vars == 0) { rb_bug("unexpected local variable"); } result = ruby_scope->local_vars[node->nd_cnt]; break; case NODE_DVAR: result = rb_dvar_ref(node->nd_vid); break; case NODE_GVAR: result = rb_gvar_get(node->nd_entry); break; case NODE_IVAR: result = rb_ivar_get(self, node->nd_vid); break; case NODE_CONST: result = ev_const_get(ruby_cref, node->nd_vid, self); break; case NODE_CVAR: result = rb_cvar_get(cvar_cbase(), node->nd_vid); break; case NODE_BLOCK_ARG: if (ruby_scope->local_vars == 0) rb_bug("unexpected block argument"); if (rb_block_given_p()) { result = rb_block_proc(); ruby_scope->local_vars[node->nd_cnt] = result; } else { result = Qnil; } break; case NODE_COLON2: { VALUE klass; klass = rb_eval(self, node->nd_head); if (rb_is_const_id(node->nd_mid)) { switch (TYPE(klass)) { case T_CLASS: case T_MODULE: result = rb_const_get_from(klass, node->nd_mid); break; default: rb_raise(rb_eTypeError, "%s is not a class/module", RSTRING(rb_obj_as_string(klass))->ptr); break; } } else { result = rb_funcall(klass, node->nd_mid, 0, 0); } } break; case NODE_COLON3: result = rb_const_get_from(rb_cObject, node->nd_mid); break; case NODE_NTH_REF: result = rb_reg_nth_match(node->nd_nth, MATCH_DATA); break; case NODE_BACK_REF: switch (node->nd_nth) { case '&': result = rb_reg_last_match(MATCH_DATA); break; case '`': result = rb_reg_match_pre(MATCH_DATA); break; case '\'': result = rb_reg_match_post(MATCH_DATA); break; case '+': result = rb_reg_match_last(MATCH_DATA); break; default: rb_bug("unexpected back-ref"); } break; case NODE_HASH: { NODE *list; VALUE hash = rb_hash_new(); VALUE key, val; list = node->nd_head; while (list) { key = rb_eval(self, list->nd_head); list = list->nd_next; if (list == 0) rb_bug("odd number list for Hash"); val = rb_eval(self, list->nd_head); list = list->nd_next; rb_hash_aset(hash, key, val); } result = hash; } break; case NODE_ZARRAY: /* zero length list */ result = rb_ary_new(); break; case NODE_ARRAY: { VALUE ary; long i; i = node->nd_alen; ary = rb_ary_new2(i); for (i=0;node;node=node->nd_next) { RARRAY(ary)->ptr[i++] = rb_eval(self, node->nd_head); RARRAY(ary)->len = i; } result = ary; } break; case NODE_STR: result = rb_str_new3(node->nd_lit); break; case NODE_EVSTR: result = rb_obj_as_string(rb_eval(self, node->nd_body)); break; case NODE_DSTR: case NODE_DXSTR: case NODE_DREGX: case NODE_DREGX_ONCE: case NODE_DSYM: { VALUE str, str2; NODE *list = node->nd_next; str = rb_str_new3(node->nd_lit); while (list) { if (list->nd_head) { switch (nd_type(list->nd_head)) { case NODE_STR: str2 = list->nd_head->nd_lit; break; default: str2 = rb_eval(self, list->nd_head); break; } rb_str_append(str, str2); OBJ_INFECT(str, str2); } list = list->nd_next; } switch (nd_type(node)) { case NODE_DREGX: result = rb_reg_new(RSTRING(str)->ptr, RSTRING(str)->len, node->nd_cflag); break; case NODE_DREGX_ONCE: /* regexp expand once */ result = rb_reg_new(RSTRING(str)->ptr, RSTRING(str)->len, node->nd_cflag); nd_set_type(node, NODE_LIT); node->nd_lit = result; break; case NODE_LIT: /* other thread may replace NODE_DREGX_ONCE to NODE_LIT */ goto again; case NODE_DXSTR: result = rb_funcall(self, '`', 1, str); break; case NODE_DSYM: result = rb_str_intern(str); break; default: result = str; break; } } break; case NODE_XSTR: result = rb_funcall(self, '`', 1, rb_str_new3(node->nd_lit)); break; case NODE_LIT: result = node->nd_lit; break; case NODE_DEFN: if (node->nd_defn) { NODE *body, *defn; VALUE origin; int noex; if (NIL_P(ruby_class)) { rb_raise(rb_eTypeError, "no class/module to add method"); } if (ruby_class == rb_cObject && node->nd_mid == init) { rb_warn("redefining Object#initialize may cause infinite loop"); } if (node->nd_mid == __id__ || node->nd_mid == __send__) { rb_warn("redefining `%s' may cause serious problem", rb_id2name(node->nd_mid)); } rb_frozen_class_p(ruby_class); body = search_method(ruby_class, node->nd_mid, &origin); if (body){ if (RTEST(ruby_verbose) && ruby_class == origin && body->nd_cnt == 0 && body->nd_body) { rb_warning("method redefined; discarding old %s", rb_id2name(node->nd_mid)); } } if (SCOPE_TEST(SCOPE_PRIVATE) || node->nd_mid == init) { noex = NOEX_PRIVATE; } else if (SCOPE_TEST(SCOPE_PROTECTED)) { noex = NOEX_PROTECTED; } else { noex = NOEX_PUBLIC; } if (body && origin == ruby_class && body->nd_body == 0) { noex |= NOEX_NOSUPER; } defn = rb_copy_node_scope(node->nd_defn, ruby_cref); rb_add_method(ruby_class, node->nd_mid, defn, noex); if (scope_vmode == SCOPE_MODFUNC) { rb_add_method(rb_singleton_class(ruby_class), node->nd_mid, defn, NOEX_PUBLIC); } result = Qnil; } break; case NODE_DEFS: if (node->nd_defn) { VALUE recv = rb_eval(self, node->nd_recv); VALUE klass; NODE *body = 0, *defn; if (ruby_safe_level >= 4 && !OBJ_TAINTED(recv)) { rb_raise(rb_eSecurityError, "Insecure: can't define singleton method"); } if (FIXNUM_P(recv) || SYMBOL_P(recv)) { rb_raise(rb_eTypeError, "can't define singleton method \"%s\" for %s", rb_id2name(node->nd_mid), rb_obj_classname(recv)); } if (OBJ_FROZEN(recv)) rb_error_frozen("object"); klass = rb_singleton_class(recv); if (st_lookup(RCLASS(klass)->m_tbl, node->nd_mid, &data)) { body = (NODE *)data; if (ruby_safe_level >= 4) { rb_raise(rb_eSecurityError, "redefining method prohibited"); } if (RTEST(ruby_verbose)) { rb_warning("redefine %s", rb_id2name(node->nd_mid)); } } defn = rb_copy_node_scope(node->nd_defn, ruby_cref); rb_add_method(klass, node->nd_mid, defn, NOEX_PUBLIC|(body?body->nd_noex&NOEX_UNDEF:0)); result = Qnil; } break; case NODE_UNDEF: if (NIL_P(ruby_class)) { rb_raise(rb_eTypeError, "no class to undef method"); } rb_undef(ruby_class, rb_to_id(rb_eval(self, node->u2.node))); result = Qnil; break; case NODE_ALIAS: if (NIL_P(ruby_class)) { rb_raise(rb_eTypeError, "no class to make alias"); } rb_alias(ruby_class, rb_to_id(rb_eval(self, node->u1.node)), rb_to_id(rb_eval(self, node->u2.node))); result = Qnil; break; case NODE_VALIAS: rb_alias_variable(node->u1.id, node->u2.id); result = Qnil; break; case NODE_CLASS: { VALUE super, klass, tmp, cbase; ID cname; int gen = Qfalse; cbase = class_prefix(self, node->nd_cpath); cname = node->nd_cpath->nd_mid; if (NIL_P(ruby_cbase)) { rb_raise(rb_eTypeError, "no outer class/module"); } if (node->nd_super) { super = rb_eval(self, node->nd_super); rb_check_inheritable(super); } else { super = 0; } if (rb_const_defined_at(cbase, cname)) { klass = rb_const_get_at(cbase, cname); if (TYPE(klass) != T_CLASS) { rb_raise(rb_eTypeError, "%s is not a class", rb_id2name(cname)); } if (super) { tmp = rb_class_real(RCLASS(klass)->super); if (tmp != super) { rb_raise(rb_eTypeError, "superclass mismatch for class %s", rb_id2name(cname)); } super = 0; } if (ruby_safe_level >= 4) { rb_raise(rb_eSecurityError, "extending class prohibited"); } } else { if (!super) super = rb_cObject; klass = rb_define_class_id(cname, super); rb_set_class_path(klass, cbase, rb_id2name(cname)); rb_const_set(cbase, cname, klass); gen = Qtrue; } if (ruby_wrapper) { rb_extend_object(klass, ruby_wrapper); rb_include_module(klass, ruby_wrapper); } if (super && gen) { rb_class_inherited(super, klass); } result = module_setup(klass, node); } break; case NODE_MODULE: { VALUE module, cbase; ID cname; if (NIL_P(ruby_cbase)) { rb_raise(rb_eTypeError, "no outer class/module"); } cbase = class_prefix(self, node->nd_cpath); cname = node->nd_cpath->nd_mid; if (rb_const_defined_at(cbase, cname)) { module = rb_const_get_at(cbase, cname); if (TYPE(module) != T_MODULE) { rb_raise(rb_eTypeError, "%s is not a module", rb_id2name(cname)); } if (ruby_safe_level >= 4) { rb_raise(rb_eSecurityError, "extending module prohibited"); } } else { module = rb_define_module_id(cname); rb_set_class_path(module, cbase, rb_id2name(cname)); rb_const_set(cbase, cname, module); } if (ruby_wrapper) { rb_extend_object(module, ruby_wrapper); rb_include_module(module, ruby_wrapper); } result = module_setup(module, node); } break; case NODE_SCLASS: { VALUE klass; result = rb_eval(self, node->nd_recv); if (FIXNUM_P(result) || SYMBOL_P(result)) { rb_raise(rb_eTypeError, "no virtual class for %s", rb_obj_classname(result)); } if (ruby_safe_level >= 4 && !OBJ_TAINTED(result)) rb_raise(rb_eSecurityError, "Insecure: can't extend object"); klass = rb_singleton_class(result); if (ruby_wrapper) { rb_extend_object(klass, ruby_wrapper); rb_include_module(klass, ruby_wrapper); } result = module_setup(klass, node); } break; case NODE_DEFINED: { char buf[20]; char *desc = is_defined(self, node->nd_head, buf); if (desc) result = rb_str_new2(desc); else result = Qnil; } break; case NODE_NEWLINE: EXEC_EVENT_HOOK(RUBY_EVENT_LINE, node, self, ruby_frame->last_func, ruby_frame->last_class); node = node->nd_next; goto again; default: unknown_node(node); } finish: CHECK_INTS; if (contnode) { node = contnode; contnode = 0; goto again; } return result; } static VALUE module_setup(module, n) VALUE module; NODE *n; { NODE * volatile node = n->nd_body; int state; struct FRAME frame; VALUE result = Qnil; /* OK */ TMP_PROTECT; frame = *ruby_frame; frame.tmp = ruby_frame; ruby_frame = &frame; PUSH_CLASS(module); PUSH_SCOPE(); PUSH_VARS(); if (node->nd_tbl) { VALUE *vars = TMP_ALLOC(node->nd_tbl[0]+1); *vars++ = (VALUE)node; ruby_scope->local_vars = vars; rb_mem_clear(ruby_scope->local_vars, node->nd_tbl[0]); ruby_scope->local_tbl = node->nd_tbl; } else { ruby_scope->local_vars = 0; ruby_scope->local_tbl = 0; } PUSH_CREF(module); PUSH_TAG(PROT_NONE); if ((state = EXEC_TAG()) == 0) { EXEC_EVENT_HOOK(RUBY_EVENT_CLASS, n, ruby_cbase, ruby_frame->last_func, ruby_frame->last_class); result = rb_eval(ruby_cbase, node->nd_next); } POP_TAG(); POP_CREF(); POP_VARS(); POP_SCOPE(); POP_CLASS(); ruby_frame = frame.tmp; EXEC_EVENT_HOOK(RUBY_EVENT_END, n, 0, ruby_frame->last_func, ruby_frame->last_class); if (state) JUMP_TAG(state); return result; } static NODE *basic_respond_to = 0; int rb_obj_respond_to(obj, id, priv) VALUE obj; ID id; int priv; { VALUE klass = CLASS_OF(obj); if (rb_method_node(klass, respond_to) == basic_respond_to) { return rb_method_boundp(klass, id, !priv); } else { VALUE args[2]; int n = 0; args[n++] = ID2SYM(id); if (priv) args[n++] = Qtrue; return RTEST(rb_funcall2(obj, respond_to, n, args)); } } int rb_respond_to(obj, id) VALUE obj; ID id; { return rb_obj_respond_to(obj, id, Qfalse); } /* * call-seq: * obj.respond_to?(symbol, include_private=false) => true or false * * Returns +true+> if _obj_ responds to the given * method. Private methods are included in the search only if the * optional second parameter evaluates to +true+. */ static VALUE obj_respond_to(argc, argv, obj) int argc; VALUE *argv; VALUE obj; { VALUE mid, priv; ID id; rb_scan_args(argc, argv, "11", &mid, &priv); id = rb_to_id(mid); if (rb_method_boundp(CLASS_OF(obj), id, !RTEST(priv))) { return Qtrue; } return Qfalse; } /* * call-seq: * mod.method_defined?(symbol) => true or false * * Returns +true+ if the named method is defined by * _mod_ (or its included modules and, if _mod_ is a class, * its ancestors). Public and protected methods are matched. * * module A * def method1() end * end * class B * def method2() end * end * class C < B * include A * def method3() end * end * * A.method_defined? :method1 #=> true * C.method_defined? "method1" #=> true * C.method_defined? "method2" #=> true * C.method_defined? "method3" #=> true * C.method_defined? "method4" #=> false */ static VALUE rb_mod_method_defined(mod, mid) VALUE mod, mid; { return rb_method_boundp(mod, rb_to_id(mid), 1); } #define VISI_CHECK(x,f) (((x)&NOEX_MASK) == (f)) /* * call-seq: * mod.public_method_defined?(symbol) => true or false * * Returns +true+ if the named public method is defined by * _mod_ (or its included modules and, if _mod_ is a class, * its ancestors). * * module A * def method1() end * end * class B * protected * def method2() end * end * class C < B * include A * def method3() end * end * * A.method_defined? :method1 #=> true * C.public_method_defined? "method1" #=> true * C.public_method_defined? "method2" #=> false * C.method_defined? "method2" #=> true */ static VALUE rb_mod_public_method_defined(mod, mid) VALUE mod, mid; { ID id = rb_to_id(mid); int noex; if (rb_get_method_body(&mod, &id, &noex)) { if (VISI_CHECK(noex, NOEX_PUBLIC)) return Qtrue; } return Qfalse; } /* * call-seq: * mod.private_method_defined?(symbol) => true or false * * Returns +true+ if the named private method is defined by * _ mod_ (or its included modules and, if _mod_ is a class, * its ancestors). * * module A * def method1() end * end * class B * private * def method2() end * end * class C < B * include A * def method3() end * end * * A.method_defined? :method1 #=> true * C.private_method_defined? "method1" #=> false * C.private_method_defined? "method2" #=> true * C.method_defined? "method2" #=> false */ static VALUE rb_mod_private_method_defined(mod, mid) VALUE mod, mid; { ID id = rb_to_id(mid); int noex; if (rb_get_method_body(&mod, &id, &noex)) { if (VISI_CHECK(noex, NOEX_PRIVATE)) return Qtrue; } return Qfalse; } /* * call-seq: * mod.protected_method_defined?(symbol) => true or false * * Returns +true+ if the named protected method is defined * by _mod_ (or its included modules and, if _mod_ is a * class, its ancestors). * * module A * def method1() end * end * class B * protected * def method2() end * end * class C < B * include A * def method3() end * end * * A.method_defined? :method1 #=> true * C.protected_method_defined? "method1" #=> false * C.protected_method_defined? "method2" #=> true * C.method_defined? "method2" #=> true */ static VALUE rb_mod_protected_method_defined(mod, mid) VALUE mod, mid; { ID id = rb_to_id(mid); int noex; if (rb_get_method_body(&mod, &id, &noex)) { if (VISI_CHECK(noex, NOEX_PROTECTED)) return Qtrue; } return Qfalse; } NORETURN(static VALUE terminate_process _((int, VALUE))); static VALUE terminate_process(status, mesg) int status; VALUE mesg; { VALUE args[2]; args[0] = INT2NUM(status); args[1] = mesg; rb_exc_raise(rb_class_new_instance(2, args, rb_eSystemExit)); } void rb_exit(status) int status; { if (prot_tag) { terminate_process(status, rb_str_new("exit", 4)); } ruby_finalize(); exit(status); } /* * call-seq: * exit(integer=0) * Kernel::exit(integer=0) * Process::exit(integer=0) * * Initiates the termination of the Ruby script by raising the * SystemExit exception. This exception may be caught. The * optional parameter is used to return a status code to the invoking * environment. * * begin * exit * puts "never get here" * rescue SystemExit * puts "rescued a SystemExit exception" * end * puts "after begin block" * * produces: * * rescued a SystemExit exception * after begin block * * Just prior to termination, Ruby executes any at_exit functions * (see Kernel::at_exit) and runs any object finalizers (see * ObjectSpace::define_finalizer). * * at_exit { puts "at_exit function" } * ObjectSpace.define_finalizer("string", proc { puts "in finalizer" }) * exit * * produces: * * at_exit function * in finalizer */ VALUE rb_f_exit(argc, argv) int argc; VALUE *argv; { VALUE status; int istatus; rb_secure(4); if (rb_scan_args(argc, argv, "01", &status) == 1) { switch (status) { case Qtrue: istatus = EXIT_SUCCESS; break; case Qfalse: istatus = EXIT_FAILURE; break; default: istatus = NUM2INT(status); #if EXIT_SUCCESS != 0 if (istatus == 0) istatus = EXIT_SUCCESS; #endif break; } } else { istatus = EXIT_SUCCESS; } rb_exit(istatus); return Qnil; /* not reached */ } /* * call-seq: * abort * Kernel::abort * Process::abort * * Terminate execution immediately, effectively by calling * Kernel.exit(1). If _msg_ is given, it is written * to STDERR prior to terminating. */ VALUE rb_f_abort(argc, argv) int argc; VALUE *argv; { rb_secure(4); if (argc == 0) { if (!NIL_P(ruby_errinfo)) { error_print(); } rb_exit(EXIT_FAILURE); } else { VALUE mesg; rb_scan_args(argc, argv, "1", &mesg); StringValue(mesg); rb_io_puts(1, &mesg, rb_stderr); terminate_process(EXIT_FAILURE, mesg); } return Qnil; /* not reached */ } void rb_iter_break() { break_jump(Qnil); } NORETURN(static void rb_longjmp _((int, VALUE))); static VALUE make_backtrace _((void)); static void rb_longjmp(tag, mesg) int tag; VALUE mesg; { VALUE at; rb_thread_t th = curr_thread; if (rb_thread_set_raised(th)) { ruby_errinfo = exception_error; JUMP_TAG(TAG_FATAL); } if (NIL_P(mesg)) mesg = ruby_errinfo; if (NIL_P(mesg)) { mesg = rb_exc_new(rb_eRuntimeError, 0, 0); } ruby_set_current_source(); if (ruby_sourcefile && !NIL_P(mesg)) { at = get_backtrace(mesg); if (NIL_P(at)) { at = make_backtrace(); if (OBJ_FROZEN(mesg)) { mesg = rb_obj_dup(mesg); } set_backtrace(mesg, at); } } if (!NIL_P(mesg)) { ruby_errinfo = mesg; } if (RTEST(ruby_debug) && !NIL_P(ruby_errinfo) && !rb_obj_is_kind_of(ruby_errinfo, rb_eSystemExit)) { VALUE e = ruby_errinfo; int status; PUSH_TAG(PROT_NONE); if ((status = EXEC_TAG()) == 0) { StringValue(e); warn_printf("Exception `%s' at %s:%d - %s\n", rb_obj_classname(ruby_errinfo), ruby_sourcefile, ruby_sourceline, RSTRING(e)->ptr); } POP_TAG(); if (status == TAG_FATAL && ruby_errinfo == exception_error) { ruby_errinfo = mesg; } else if (status) { rb_thread_reset_raised(th); JUMP_TAG(status); } } rb_trap_restore_mask(); if (tag != TAG_FATAL) { EXEC_EVENT_HOOK(RUBY_EVENT_RAISE, ruby_current_node, ruby_frame->self, ruby_frame->last_func, ruby_frame->last_class); } if (!prot_tag) { error_print(); } rb_thread_raised_clear(th); JUMP_TAG(tag); } void rb_exc_jump(mesg) VALUE mesg; { rb_thread_raised_clear(rb_curr_thread); ruby_errinfo = mesg; JUMP_TAG(TAG_RAISE); } void rb_exc_raise(mesg) VALUE mesg; { rb_longjmp(TAG_RAISE, mesg); } void rb_exc_fatal(mesg) VALUE mesg; { rb_longjmp(TAG_FATAL, mesg); } void rb_interrupt() { rb_raise(rb_eInterrupt, ""); } /* * call-seq: * raise * raise(string) * raise(exception [, string [, array]]) * fail * fail(string) * fail(exception [, string [, array]]) * * With no arguments, raises the exception in $! or raises * a RuntimeError if $! is +nil+. * With a single +String+ argument, raises a * +RuntimeError+ with the string as a message. Otherwise, * the first parameter should be the name of an +Exception+ * class (or an object that returns an +Exception+ object when sent * an +exception+ message). The optional second parameter sets the * message associated with the exception, and the third parameter is an * array of callback information. Exceptions are caught by the * +rescue+ clause of begin...end blocks. * * raise "Failed to create socket" * raise ArgumentError, "No parameters", caller */ static VALUE rb_f_raise(argc, argv) int argc; VALUE *argv; { rb_raise_jump(rb_make_exception(argc, argv)); return Qnil; /* not reached */ } static VALUE rb_make_exception(argc, argv) int argc; VALUE *argv; { VALUE mesg; ID exception; int n; mesg = Qnil; switch (argc) { case 0: mesg = Qnil; break; case 1: if (NIL_P(argv[0])) break; if (TYPE(argv[0]) == T_STRING) { mesg = rb_exc_new3(rb_eRuntimeError, argv[0]); break; } n = 0; goto exception_call; case 2: case 3: n = 1; exception_call: exception = rb_intern("exception"); if (!rb_respond_to(argv[0], exception)) { rb_raise(rb_eTypeError, "exception class/object expected"); } mesg = rb_funcall(argv[0], exception, n, argv[1]); break; default: rb_raise(rb_eArgError, "wrong number of arguments"); break; } if (argc > 0) { if (!rb_obj_is_kind_of(mesg, rb_eException)) rb_raise(rb_eTypeError, "exception object expected"); if (argc>2) set_backtrace(mesg, argv[2]); } return mesg; } static void rb_raise_jump(mesg) VALUE mesg; { if (ruby_frame != top_frame) { PUSH_FRAME(); /* fake frame */ *ruby_frame = *_frame.prev->prev; rb_longjmp(TAG_RAISE, mesg); POP_FRAME(); } rb_longjmp(TAG_RAISE, mesg); } void rb_jump_tag(tag) int tag; { JUMP_TAG(tag); } int rb_block_given_p() { if (ruby_frame->iter == ITER_CUR && ruby_block) return Qtrue; return Qfalse; } int rb_iterator_p() { return rb_block_given_p(); } /* * call-seq: * block_given? => true or false * iterator? => true or false * * Returns true if yield would execute a * block in the current context. The iterator? form * is mildly deprecated. * * def try * if block_given? * yield * else * "no block" * end * end * try #=> "no block" * try { "hello" } #=> "hello" * try do "hello" end #=> "hello" */ static VALUE rb_f_block_given_p() { if (ruby_frame->prev && ruby_frame->prev->iter == ITER_CUR && ruby_block) return Qtrue; return Qfalse; } VALUE rb_eThreadError; NORETURN(static void proc_jump_error(int, VALUE)); static void proc_jump_error(state, result) int state; VALUE result; { char mesg[32]; char *statement; switch (state) { case TAG_BREAK: statement = "break"; break; case TAG_RETURN: statement = "return"; break; case TAG_RETRY: statement = "retry"; break; default: statement = "local-jump"; break; /* should not happen */ } snprintf(mesg, sizeof mesg, "%s from proc-closure", statement); localjump_error(mesg, result, state); } static void return_jump(retval) VALUE retval; { struct tag *tt = prot_tag; int yield = Qfalse; if (retval == Qundef) retval = Qnil; while (tt) { if (tt->tag == PROT_YIELD) { yield = Qtrue; tt = tt->prev; } if (tt->tag == PROT_FUNC && tt->frame->uniq == ruby_frame->uniq) { tt->dst = (VALUE)ruby_frame->uniq; tt->retval = retval; JUMP_TAG(TAG_RETURN); } if (tt->tag == PROT_LAMBDA && !yield) { tt->dst = (VALUE)tt->frame->uniq; tt->retval = retval; JUMP_TAG(TAG_RETURN); } if (tt->tag == PROT_THREAD) { rb_raise(rb_eThreadError, "return can't jump across threads"); } tt = tt->prev; } localjump_error("unexpected return", retval, TAG_RETURN); } static void break_jump(retval) VALUE retval; { struct tag *tt = prot_tag; if (retval == Qundef) retval = Qnil; while (tt) { switch (tt->tag) { case PROT_THREAD: case PROT_YIELD: case PROT_LOOP: case PROT_LAMBDA: tt->dst = (VALUE)tt->frame->uniq; tt->retval = retval; JUMP_TAG(TAG_BREAK); break; case PROT_FUNC: tt = 0; continue; default: break; } tt = tt->prev; } localjump_error("unexpected break", retval, TAG_BREAK); } static void next_jump(retval) VALUE retval; { struct tag *tt = prot_tag; if (retval == Qundef) retval = Qnil; while (tt) { switch (tt->tag) { case PROT_THREAD: case PROT_YIELD: case PROT_LOOP: case PROT_LAMBDA: case PROT_FUNC: tt->dst = (VALUE)tt->frame->uniq; tt->retval = retval; JUMP_TAG(TAG_NEXT); break; default: break; } tt = tt->prev; } localjump_error("unexpected next", retval, TAG_NEXT); } void rb_need_block() { if (!rb_block_given_p()) { localjump_error("no block given", Qnil, 0); } } static VALUE rb_yield_0(val, self, klass, flags, avalue) VALUE val, self, klass; /* OK */ int flags, avalue; { NODE *node; volatile VALUE result = Qnil; volatile VALUE old_cref; volatile VALUE old_wrapper; struct BLOCK * volatile block; struct SCOPE * volatile old_scope; int old_vmode; struct FRAME frame; NODE *cnode = ruby_current_node; int lambda = flags & YIELD_LAMBDA_CALL; int state; rb_need_block(); PUSH_VARS(); block = ruby_block; frame = block->frame; frame.prev = ruby_frame; frame.node = cnode; ruby_frame = &(frame); old_cref = (VALUE)ruby_cref; ruby_cref = block->cref; old_wrapper = ruby_wrapper; ruby_wrapper = block->wrapper; old_scope = ruby_scope; ruby_scope = block->scope; old_vmode = scope_vmode; scope_vmode = (flags & YIELD_PUBLIC_DEF) ? SCOPE_PUBLIC : block->vmode; ruby_block = block->prev; if (block->flags & BLOCK_D_SCOPE) { /* put place holder for dynamic (in-block) local variables */ ruby_dyna_vars = new_dvar(0, 0, block->dyna_vars); } else { /* FOR does not introduce new scope */ ruby_dyna_vars = block->dyna_vars; } PUSH_CLASS(klass ? klass : block->klass); if (!klass) { self = block->self; } node = block->body; if (block->var) { PUSH_TAG(PROT_NONE); if ((state = EXEC_TAG()) == 0) { if (block->var == (NODE*)1) { /* no parameter || */ if (lambda && RARRAY(val)->len != 0) { rb_raise(rb_eArgError, "wrong number of arguments (%ld for 0)", RARRAY(val)->len); } } else if (block->var == (NODE*)2) { if (TYPE(val) == T_ARRAY && RARRAY(val)->len != 0) { rb_raise(rb_eArgError, "wrong number of arguments (%ld for 0)", RARRAY(val)->len); } } else if (nd_type(block->var) == NODE_MASGN) { if (!avalue) { val = svalue_to_mrhs(val, block->var->nd_head); } massign(self, block->var, val, lambda); } else { int len = 0; if (avalue) { len = RARRAY(val)->len; if (len == 0) { goto zero_arg; } if (len == 1) { val = RARRAY(val)->ptr[0]; } else { goto multi_values; } } else if (val == Qundef) { zero_arg: val = Qnil; multi_values: { ruby_current_node = block->var; rb_warn("multiple values for a block parameter (%d for 1)\n\tfrom %s:%d", len, cnode->nd_file, nd_line(cnode)); ruby_current_node = cnode; } } assign(self, block->var, val, lambda); } } POP_TAG(); if (state) goto pop_state; } if (!node) { state = 0; goto pop_state; } ruby_current_node = node; PUSH_ITER(block->iter); PUSH_TAG(lambda ? PROT_NONE : PROT_YIELD); if ((state = EXEC_TAG()) == 0) { redo: if (nd_type(node) == NODE_CFUNC || nd_type(node) == NODE_IFUNC) { if (node->nd_state == YIELD_FUNC_AVALUE) { if (!avalue) { val = svalue_to_avalue(val); } } else { if (avalue) { val = avalue_to_svalue(val); } if (val == Qundef && node->nd_state != YIELD_FUNC_SVALUE) val = Qnil; } result = (*node->nd_cfnc)(val, node->nd_tval, self); } else { result = rb_eval(self, node); } } else { switch (state) { case TAG_REDO: state = 0; CHECK_INTS; goto redo; case TAG_NEXT: if (!lambda) { state = 0; result = prot_tag->retval; } break; case TAG_BREAK: if (TAG_DST()) { result = prot_tag->retval; } else { lambda = Qtrue; /* just pass TAG_BREAK */ } break; default: break; } } POP_TAG(); POP_ITER(); pop_state: POP_CLASS(); if (ruby_dyna_vars && (block->flags & BLOCK_D_SCOPE) && !FL_TEST(ruby_dyna_vars, DVAR_DONT_RECYCLE)) { struct RVarmap *vars = ruby_dyna_vars; if (ruby_dyna_vars->id == 0) { vars = ruby_dyna_vars->next; rb_gc_force_recycle((VALUE)ruby_dyna_vars); while (vars && vars->id != 0 && vars != block->dyna_vars) { struct RVarmap *tmp = vars->next; rb_gc_force_recycle((VALUE)vars); vars = tmp; } } } POP_VARS(); ruby_block = block; ruby_frame = ruby_frame->prev; ruby_cref = (NODE*)old_cref; ruby_wrapper = old_wrapper; if (ruby_scope->flags & SCOPE_DONT_RECYCLE) scope_dup(old_scope); ruby_scope = old_scope; scope_vmode = old_vmode; switch (state) { case 0: break; case TAG_BREAK: if (!lambda) { struct tag *tt = prot_tag; while (tt) { if (tt->tag == PROT_LOOP && tt->blkid == ruby_block->uniq) { tt->dst = (VALUE)tt->frame->uniq; tt->retval = result; JUMP_TAG(TAG_BREAK); } tt = tt->prev; } proc_jump_error(TAG_BREAK, result); } /* fall through */ default: JUMP_TAG(state); break; } ruby_current_node = cnode; return result; } VALUE rb_yield(val) VALUE val; { return rb_yield_0(val, 0, 0, 0, Qfalse); } VALUE #ifdef HAVE_STDARG_PROTOTYPES rb_yield_values(int n, ...) #else rb_yield_values(n, va_alist) int n; va_dcl #endif { va_list args; VALUE ary; if (n == 0) { return rb_yield_0(Qundef, 0, 0, 0, Qfalse); } ary = rb_ary_new2(n); va_init_list(args, n); while (n--) { rb_ary_push(ary, va_arg(args, VALUE)); } va_end(args); return rb_yield_0(ary, 0, 0, 0, Qtrue); } VALUE rb_yield_splat(values) VALUE values; { int avalue = Qfalse; if (TYPE(values) == T_ARRAY) { if (RARRAY(values)->len == 0) { values = Qundef; } else { avalue = Qtrue; } } return rb_yield_0(values, 0, 0, 0, avalue); } /* * call-seq: * loop {|| block } * * Repeatedly executes the block. * * loop do * print "Input: " * line = gets * break if !line or line =~ /^qQ/ * # ... * end */ static VALUE rb_f_loop() { for (;;) { rb_yield_0(Qundef, 0, 0, 0, Qfalse); CHECK_INTS; } return Qnil; /* dummy */ } static VALUE massign(self, node, val, pcall) VALUE self; NODE *node; VALUE val; int pcall; { NODE *list; long i = 0, len; len = RARRAY(val)->len; list = node->nd_head; for (; list && ind_head, RARRAY(val)->ptr[i], pcall); list = list->nd_next; } if (pcall && list) goto arg_error; if (node->nd_args) { if ((long)(node->nd_args) == -1) { /* no check for mere `*' */ } else if (!list && ind_args, rb_ary_new4(len-i, RARRAY(val)->ptr+i), pcall); } else { assign(self, node->nd_args, rb_ary_new2(0), pcall); } } else if (pcall && i < len) { goto arg_error; } while (list) { i++; assign(self, list->nd_head, Qnil, pcall); list = list->nd_next; } return val; arg_error: while (list) { i++; list = list->nd_next; } rb_raise(rb_eArgError, "wrong number of arguments (%ld for %ld)", len, i); } static void assign(self, lhs, val, pcall) VALUE self; NODE *lhs; VALUE val; int pcall; { ruby_current_node = lhs; if (val == Qundef) { rb_warning("assigning void value"); val = Qnil; } switch (nd_type(lhs)) { case NODE_GASGN: rb_gvar_set(lhs->nd_entry, val); break; case NODE_IASGN: rb_ivar_set(self, lhs->nd_vid, val); break; case NODE_LASGN: if (ruby_scope->local_vars == 0) rb_bug("unexpected local variable assignment"); ruby_scope->local_vars[lhs->nd_cnt] = val; break; case NODE_DASGN: dvar_asgn(lhs->nd_vid, val); break; case NODE_DASGN_CURR: dvar_asgn_curr(lhs->nd_vid, val); break; case NODE_CDECL: if (lhs->nd_vid == 0) { rb_const_set(class_prefix(self, lhs->nd_else), lhs->nd_else->nd_mid, val); } else { rb_const_set(ruby_cbase, lhs->nd_vid, val); } break; case NODE_CVDECL: if (RTEST(ruby_verbose) && FL_TEST(ruby_cbase, FL_SINGLETON)) { rb_warn("declaring singleton class variable"); } rb_cvar_set(cvar_cbase(), lhs->nd_vid, val, Qtrue); break; case NODE_CVASGN: rb_cvar_set(cvar_cbase(), lhs->nd_vid, val, Qfalse); break; case NODE_MASGN: massign(self, lhs, svalue_to_mrhs(val, lhs->nd_head), pcall); break; case NODE_CALL: case NODE_ATTRASGN: { VALUE recv; int scope; if (lhs->nd_recv == (NODE *)1) { recv = self; scope = 1; } else { recv = rb_eval(self, lhs->nd_recv); scope = 0; } if (!lhs->nd_args) { /* attr set */ ruby_current_node = lhs; SET_CURRENT_SOURCE(); rb_call(CLASS_OF(recv), recv, lhs->nd_mid, 1, &val, scope, self); } else { /* array set */ VALUE args; args = rb_eval(self, lhs->nd_args); rb_ary_push(args, val); ruby_current_node = lhs; SET_CURRENT_SOURCE(); rb_call(CLASS_OF(recv), recv, lhs->nd_mid, RARRAY(args)->len, RARRAY(args)->ptr, scope, self); } } break; default: rb_bug("bug in variable assignment"); break; } } VALUE rb_iterate(it_proc, data1, bl_proc, data2) VALUE (*it_proc) _((VALUE)), (*bl_proc)(ANYARGS); VALUE data1, data2; { int state; volatile VALUE retval = Qnil; NODE *node = NEW_IFUNC(bl_proc, data2); VALUE self = ruby_top_self; PUSH_TAG(PROT_LOOP); PUSH_BLOCK(0, node); PUSH_ITER(ITER_PRE); state = EXEC_TAG(); if (state == 0) { iter_retry: retval = (*it_proc)(data1); } else if (state == TAG_BREAK && TAG_DST()) { retval = prot_tag->retval; state = 0; } else if (state == TAG_RETRY) { state = 0; goto iter_retry; } POP_ITER(); POP_BLOCK(); POP_TAG(); switch (state) { case 0: break; default: JUMP_TAG(state); } return retval; } static int handle_rescue(self, node) VALUE self; NODE *node; { int argc; VALUE *argv; /* used in SETUP_ARGS */ TMP_PROTECT; if (!node->nd_args) { return rb_obj_is_kind_of(ruby_errinfo, rb_eStandardError); } BEGIN_CALLARGS; SETUP_ARGS(node->nd_args); END_CALLARGS; while (argc--) { if (!rb_obj_is_kind_of(argv[0], rb_cModule)) { rb_raise(rb_eTypeError, "class or module required for rescue clause"); } if (RTEST(rb_funcall(*argv, eqq, 1, ruby_errinfo))) return 1; argv++; } return 0; } VALUE #ifdef HAVE_STDARG_PROTOTYPES rb_rescue2(VALUE (*b_proc)(ANYARGS), VALUE data1, VALUE (*r_proc)(ANYARGS), VALUE data2, ...) #else rb_rescue2(b_proc, data1, r_proc, data2, va_alist) VALUE (*b_proc)(ANYARGS), (*r_proc)(ANYARGS); VALUE data1, data2; va_dcl #endif { int state; volatile VALUE result; volatile VALUE e_info = ruby_errinfo; volatile int handle = Qfalse; VALUE eclass; va_list args; PUSH_TAG(PROT_NONE); switch (state = EXEC_TAG()) { case TAG_RETRY: if (!handle) break; handle = Qfalse; state = 0; ruby_errinfo = Qnil; case 0: result = (*b_proc)(data1); break; case TAG_RAISE: if (handle) break; handle = Qfalse; va_init_list(args, data2); while ((eclass = va_arg(args, VALUE)) != 0) { if (rb_obj_is_kind_of(ruby_errinfo, eclass)) { handle = Qtrue; break; } } va_end(args); if (handle) { state = 0; if (r_proc) { result = (*r_proc)(data2, ruby_errinfo); } else { result = Qnil; } ruby_errinfo = e_info; } } POP_TAG(); if (state) JUMP_TAG(state); return result; } VALUE rb_rescue(b_proc, data1, r_proc, data2) VALUE (*b_proc)(), (*r_proc)(); VALUE data1, data2; { return rb_rescue2(b_proc, data1, r_proc, data2, rb_eStandardError, (VALUE)0); } static VALUE cont_protect; VALUE rb_protect(proc, data, state) VALUE (*proc) _((VALUE)); VALUE data; int *state; { VALUE result = Qnil; /* OK */ int status; PUSH_TAG(PROT_NONE); cont_protect = (VALUE)rb_node_newnode(NODE_MEMO, cont_protect, 0, 0); if ((status = EXEC_TAG()) == 0) { result = (*proc)(data); } cont_protect = ((NODE *)cont_protect)->u1.value; POP_TAG(); if (state) { *state = status; } if (status != 0) { return Qnil; } return result; } VALUE rb_ensure(b_proc, data1, e_proc, data2) VALUE (*b_proc)(); VALUE data1; VALUE (*e_proc)(); VALUE data2; { int state; volatile VALUE result = Qnil; VALUE retval; PUSH_TAG(PROT_NONE); if ((state = EXEC_TAG()) == 0) { result = (*b_proc)(data1); } POP_TAG(); retval = prot_tag ? prot_tag->retval : Qnil; /* save retval */ if (!thread_no_ensure()) { (*e_proc)(data2); } if (prot_tag) return_value(retval); if (state) JUMP_TAG(state); return result; } VALUE rb_with_disable_interrupt(proc, data) VALUE (*proc)(); VALUE data; { VALUE result = Qnil; /* OK */ int status; DEFER_INTS; { int thr_critical = rb_thread_critical; rb_thread_critical = Qtrue; PUSH_TAG(PROT_NONE); if ((status = EXEC_TAG()) == 0) { result = (*proc)(data); } POP_TAG(); rb_thread_critical = thr_critical; } ENABLE_INTS; if (status) JUMP_TAG(status); return result; } static void stack_check() { rb_thread_t th = rb_curr_thread; if (!rb_thread_raised_p(th, RAISED_STACKOVERFLOW) && ruby_stack_check()) { rb_thread_raised_set(th, RAISED_STACKOVERFLOW); rb_exc_raise(sysstack_error); } } static int last_call_status; #define CSTAT_PRIV 1 #define CSTAT_PROT 2 #define CSTAT_VCALL 4 #define CSTAT_SUPER 8 /* * call-seq: * obj.method_missing(symbol [, *args] ) => result * * Invoked by Ruby when obj is sent a message it cannot handle. * symbol is the symbol for the method called, and args * are any arguments that were passed to it. By default, the interpreter * raises an error when this method is called. However, it is possible * to override the method to provide more dynamic behavior. * The example below creates * a class Roman, which responds to methods with names * consisting of roman numerals, returning the corresponding integer * values. * * class Roman * def romanToInt(str) * # ... * end * def method_missing(methId) * str = methId.id2name * romanToInt(str) * end * end * * r = Roman.new * r.iv #=> 4 * r.xxiii #=> 23 * r.mm #=> 2000 */ static VALUE rb_method_missing(argc, argv, obj) int argc; VALUE *argv; VALUE obj; { ID id; VALUE exc = rb_eNoMethodError; char *format = 0; NODE *cnode = ruby_current_node; if (argc == 0 || !SYMBOL_P(argv[0])) { rb_raise(rb_eArgError, "no id given"); } stack_check(); id = SYM2ID(argv[0]); if (last_call_status & CSTAT_PRIV) { format = "private method `%s' called for %s"; } else if (last_call_status & CSTAT_PROT) { format = "protected method `%s' called for %s"; } else if (last_call_status & CSTAT_VCALL) { format = "undefined local variable or method `%s' for %s"; exc = rb_eNameError; } else if (last_call_status & CSTAT_SUPER) { format = "super: no superclass method `%s'"; } if (!format) { format = "undefined method `%s' for %s"; } ruby_current_node = cnode; { int n = 0; VALUE args[3]; args[n++] = rb_funcall(rb_const_get(exc, rb_intern("message")), '!', 3, rb_str_new2(format), obj, argv[0]); args[n++] = argv[0]; if (exc == rb_eNoMethodError) { args[n++] = rb_ary_new4(argc-1, argv+1); } exc = rb_class_new_instance(n, args, exc); ruby_frame = ruby_frame->prev; /* pop frame for "method_missing" */ rb_exc_raise(exc); } return Qnil; /* not reached */ } static VALUE method_missing(obj, id, argc, argv, call_status) VALUE obj; ID id; int argc; const VALUE *argv; int call_status; { VALUE *nargv; last_call_status = call_status; if (id == missing) { PUSH_FRAME(); rb_method_missing(argc, argv, obj); POP_FRAME(); } else if (id == ID_ALLOCATOR) { rb_raise(rb_eTypeError, "allocator undefined for %s", rb_class2name(obj)); } if (argc < 0) { VALUE tmp; argc = -argc-1; tmp = splat_value(argv[argc]); nargv = ALLOCA_N(VALUE, argc + RARRAY(tmp)->len + 1); MEMCPY(nargv+1, argv, VALUE, argc); MEMCPY(nargv+1+argc, RARRAY(tmp)->ptr, VALUE, RARRAY(tmp)->len); argc += RARRAY(tmp)->len; } else { nargv = ALLOCA_N(VALUE, argc+1); MEMCPY(nargv+1, argv, VALUE, argc); } nargv[0] = ID2SYM(id); return rb_funcall2(obj, missing, argc+1, nargv); } static inline VALUE call_cfunc(func, recv, len, argc, argv) VALUE (*func)(); VALUE recv; int len, argc; VALUE *argv; { if (len >= 0 && argc != len) { rb_raise(rb_eArgError, "wrong number of arguments (%d for %d)", argc, len); } switch (len) { case -2: return (*func)(recv, rb_ary_new4(argc, argv)); break; case -1: return (*func)(argc, argv, recv); break; case 0: return (*func)(recv); break; case 1: return (*func)(recv, argv[0]); break; case 2: return (*func)(recv, argv[0], argv[1]); break; case 3: return (*func)(recv, argv[0], argv[1], argv[2]); break; case 4: return (*func)(recv, argv[0], argv[1], argv[2], argv[3]); break; case 5: return (*func)(recv, argv[0], argv[1], argv[2], argv[3], argv[4]); break; case 6: return (*func)(recv, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]); break; case 7: return (*func)(recv, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6]); break; case 8: return (*func)(recv, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7]); break; case 9: return (*func)(recv, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8]); break; case 10: return (*func)(recv, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9]); break; case 11: return (*func)(recv, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10]); break; case 12: return (*func)(recv, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11]); break; case 13: return (*func)(recv, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12]); break; case 14: return (*func)(recv, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13]); break; case 15: return (*func)(recv, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9], argv[10], argv[11], argv[12], argv[13], argv[14]); break; default: rb_raise(rb_eArgError, "too many arguments (%d)", len); break; } return Qnil; /* not reached */ } static VALUE rb_call0(klass, recv, id, oid, argc, argv, body, flags) VALUE klass, recv; ID id; ID oid; int argc; /* OK */ VALUE *argv; /* OK */ NODE * volatile body; int flags; { NODE *b2; /* OK */ volatile VALUE result = Qnil; int itr; static int tick; TMP_PROTECT; volatile int safe = -1; if (NOEX_SAFE(flags) > ruby_safe_level && NOEX_SAFE(flags) > 2) { rb_raise(rb_eSecurityError, "calling insecure method: %s", rb_id2name(id)); } switch (ruby_iter->iter) { case ITER_PRE: case ITER_PAS: itr = ITER_CUR; break; case ITER_CUR: default: itr = ITER_NOT; break; } if ((++tick & 0xff) == 0) { CHECK_INTS; /* better than nothing */ stack_check(); rb_gc_finalize_deferred(); } if (argc < 0) { VALUE tmp; VALUE *nargv; argc = -argc-1; tmp = splat_value(argv[argc]); nargv = TMP_ALLOC(argc + RARRAY(tmp)->len); MEMCPY(nargv, argv, VALUE, argc); MEMCPY(nargv+argc, RARRAY(tmp)->ptr, VALUE, RARRAY(tmp)->len); argc += RARRAY(tmp)->len; argv = nargv; } PUSH_ITER(itr); PUSH_FRAME(); ruby_frame->last_func = id; ruby_frame->orig_func = oid; ruby_frame->last_class = (flags & NOEX_NOSUPER)?0:klass; ruby_frame->self = recv; ruby_frame->argc = argc; ruby_frame->flags = 0; switch (nd_type(body)) { case NODE_CFUNC: { int len = body->nd_argc; if (len < -2) { rb_bug("bad argc (%d) specified for `%s(%s)'", len, rb_class2name(klass), rb_id2name(id)); } if (event_hooks) { int state; EXEC_EVENT_HOOK(RUBY_EVENT_C_CALL, ruby_current_node, recv, id, klass); PUSH_TAG(PROT_FUNC); if ((state = EXEC_TAG()) == 0) { result = call_cfunc(body->nd_cfnc, recv, len, argc, argv); } POP_TAG(); ruby_current_node = ruby_frame->node; EXEC_EVENT_HOOK(RUBY_EVENT_C_RETURN, ruby_current_node, recv, id, klass); if (state) JUMP_TAG(state); } else { result = call_cfunc(body->nd_cfnc, recv, len, argc, argv); } } break; /* for attr get/set */ case NODE_IVAR: if (argc != 0) { rb_raise(rb_eArgError, "wrong number of arguments (%d for 0)", argc); } result = rb_attr_get(recv, body->nd_vid); break; case NODE_ATTRSET: if (argc != 1) rb_raise(rb_eArgError, "wrong number of arguments (%d for 1)", argc); result = rb_ivar_set(recv, body->nd_vid, argv[0]); break; case NODE_ZSUPER: result = rb_call_super(argc, argv); break; case NODE_DMETHOD: result = method_call(argc, argv, umethod_bind(body->nd_cval, recv)); break; case NODE_BMETHOD: ruby_frame->flags |= FRAME_DMETH; if (event_hooks) { struct BLOCK *data; Data_Get_Struct(body->nd_cval, struct BLOCK, data); EXEC_EVENT_HOOK(RUBY_EVENT_CALL, data->body, recv, id, klass); } result = proc_invoke(body->nd_cval, rb_ary_new4(argc, argv), recv, klass); if (event_hooks) { EXEC_EVENT_HOOK(RUBY_EVENT_RETURN, body, recv, id, klass); } break; case NODE_SCOPE: { int state; VALUE *local_vars; /* OK */ NODE *saved_cref = 0; PUSH_SCOPE(); if (body->nd_rval) { saved_cref = ruby_cref; ruby_cref = (NODE*)body->nd_rval; } PUSH_CLASS(ruby_cbase); if (body->nd_tbl) { local_vars = TMP_ALLOC(body->nd_tbl[0]+1); *local_vars++ = (VALUE)body; rb_mem_clear(local_vars, body->nd_tbl[0]); ruby_scope->local_tbl = body->nd_tbl; ruby_scope->local_vars = local_vars; } else { local_vars = ruby_scope->local_vars = 0; ruby_scope->local_tbl = 0; } b2 = body = body->nd_next; if (NOEX_SAFE(flags) > ruby_safe_level) { safe = ruby_safe_level; ruby_safe_level = NOEX_SAFE(flags); } PUSH_VARS(); PUSH_TAG(PROT_FUNC); if ((state = EXEC_TAG()) == 0) { NODE *node = 0; int i, nopt = 0; if (nd_type(body) == NODE_ARGS) { node = body; body = 0; } else if (nd_type(body) == NODE_BLOCK) { node = body->nd_head; body = body->nd_next; } if (node) { if (nd_type(node) != NODE_ARGS) { rb_bug("no argument-node"); } i = node->nd_cnt; if (i > argc) { rb_raise(rb_eArgError, "wrong number of arguments (%d for %d)", argc, i); } if (!node->nd_rest) { NODE *optnode = node->nd_opt; nopt = i; while (optnode) { nopt++; optnode = optnode->nd_next; } if (nopt < argc) { rb_raise(rb_eArgError, "wrong number of arguments (%d for %d)", argc, nopt); } } if (local_vars) { if (i > 0) { /* +2 for $_ and $~ */ MEMCPY(local_vars+2, argv, VALUE, i); } } argv += i; argc -= i; if (node->nd_opt) { NODE *opt = node->nd_opt; while (opt && argc) { assign(recv, opt->nd_head, *argv, 1); argv++; argc--; ++i; opt = opt->nd_next; } if (opt) { rb_eval(recv, opt); while (opt) { opt = opt->nd_next; ++i; } } } if (!node->nd_rest) { i = nopt; } else { VALUE v; if (argc > 0) { v = rb_ary_new4(argc,argv); i = -i - 1; } else { v = rb_ary_new2(0); } assign(recv, node->nd_rest, v, 1); } ruby_frame->argc = i; } if (event_hooks) { EXEC_EVENT_HOOK(RUBY_EVENT_CALL, b2, recv, id, klass); } result = rb_eval(recv, body); } else if (state == TAG_RETURN && TAG_DST()) { result = prot_tag->retval; state = 0; } POP_TAG(); if (event_hooks) { EXEC_EVENT_HOOK(RUBY_EVENT_RETURN, body, recv, id, klass); } POP_VARS(); POP_CLASS(); POP_SCOPE(); ruby_cref = saved_cref; if (safe >= 0) ruby_safe_level = safe; switch (state) { case 0: break; case TAG_BREAK: case TAG_RETURN: JUMP_TAG(state); break; case TAG_RETRY: if (rb_block_given_p()) JUMP_TAG(state); /* fall through */ default: jump_tag_but_local_jump(state, result); break; } } break; default: unknown_node(body); break; } POP_FRAME(); POP_ITER(); return result; } static VALUE rb_call(klass, recv, mid, argc, argv, scope, self) VALUE klass, recv; ID mid; int argc; /* OK */ const VALUE *argv; /* OK */ int scope; VALUE self; { NODE *body; /* OK */ int noex; ID id = mid; struct cache_entry *ent; if (!klass) { rb_raise(rb_eNotImpError, "method `%s' called on terminated object (0x%lx)", rb_id2name(mid), recv); } /* is it in the method cache? */ ent = cache + EXPR1(klass, mid); if (ent->mid == mid && ent->klass == klass) { if (!ent->method) return method_missing(recv, mid, argc, argv, scope==2?CSTAT_VCALL:0); klass = ent->origin; id = ent->mid0; noex = ent->noex; body = ent->method; } else if ((body = rb_get_method_body(&klass, &id, &noex)) == 0) { if (scope == 3) { return method_missing(recv, mid, argc, argv, CSTAT_SUPER); } return method_missing(recv, mid, argc, argv, scope==2?CSTAT_VCALL:0); } if (mid != missing && scope == 0) { /* receiver specified form for private method */ if (noex & NOEX_PRIVATE) return method_missing(recv, mid, argc, argv, CSTAT_PRIV); /* self must be kind of a specified form for protected method */ if (noex & NOEX_PROTECTED) { VALUE defined_class = klass; if (self == Qundef) self = ruby_frame->self; if (TYPE(defined_class) == T_ICLASS) { defined_class = RBASIC(defined_class)->klass; } if (!rb_obj_is_kind_of(self, rb_class_real(defined_class))) return method_missing(recv, mid, argc, argv, CSTAT_PROT); } } return rb_call0(klass, recv, mid, id, argc, argv, body, noex); } VALUE rb_apply(recv, mid, args) VALUE recv; ID mid; VALUE args; { int argc; VALUE *argv; argc = RARRAY(args)->len; /* Assigns LONG, but argc is INT */ argv = ALLOCA_N(VALUE, argc); MEMCPY(argv, RARRAY(args)->ptr, VALUE, argc); return rb_call(CLASS_OF(recv), recv, mid, argc, argv, 1, Qundef); } /* * call-seq: * obj.send(symbol [, args...]) => obj * obj.__send__(symbol [, args...]) => obj * * Invokes the method identified by _symbol_, passing it any * arguments specified. You can use \_\_send__ if the name * +send+ clashes with an existing method in _obj_. * * class Klass * def hello(*args) * "Hello " + args.join(' ') * end * end * k = Klass.new * k.send :hello, "gentle", "readers" #=> "Hello gentle readers" */ static VALUE rb_f_send(argc, argv, recv) int argc; VALUE *argv; VALUE recv; { VALUE vid; if (argc == 0) rb_raise(rb_eArgError, "no method name given"); vid = *argv++; argc--; PUSH_ITER(rb_block_given_p()?ITER_PRE:ITER_NOT); vid = rb_call(CLASS_OF(recv), recv, rb_to_id(vid), argc, argv, 1, Qundef); POP_ITER(); return vid; } static VALUE vafuncall(recv, mid, n, ar) VALUE recv; ID mid; int n; va_list *ar; { VALUE *argv; if (n > 0) { long i; argv = ALLOCA_N(VALUE, n); for (i=0;ilast_class == 0) { rb_name_error(ruby_frame->last_func, "calling `super' from `%s' is prohibited", rb_id2name(ruby_frame->orig_func)); } self = ruby_frame->self; klass = ruby_frame->last_class; if (RCLASS(klass)->super == 0) { return method_missing(self, ruby_frame->orig_func, argc, argv, CSTAT_SUPER); } PUSH_ITER(ruby_iter->iter ? ITER_PRE : ITER_NOT); result = rb_call(RCLASS(klass)->super, self, ruby_frame->orig_func, argc, argv, 3, Qundef); POP_ITER(); return result; } static VALUE backtrace(lev) int lev; { struct FRAME *frame = ruby_frame; char buf[BUFSIZ]; VALUE ary; NODE *n; ary = rb_ary_new(); if (frame->last_func == ID_ALLOCATOR) { frame = frame->prev; } if (lev < 0) { ruby_set_current_source(); if (frame->last_func) { snprintf(buf, BUFSIZ, "%s:%d:in `%s'", ruby_sourcefile, ruby_sourceline, rb_id2name(frame->last_func)); } else if (ruby_sourceline == 0) { snprintf(buf, BUFSIZ, "%s", ruby_sourcefile); } else { snprintf(buf, BUFSIZ, "%s:%d", ruby_sourcefile, ruby_sourceline); } rb_ary_push(ary, rb_str_new2(buf)); if (lev < -1) return ary; } else { while (lev-- > 0) { frame = frame->prev; if (!frame) { ary = Qnil; break; } } } for (; frame && (n = frame->node); frame = frame->prev) { if (frame->prev && frame->prev->last_func) { if (frame->prev->node == n) { if (frame->prev->last_func == frame->last_func) continue; } snprintf(buf, BUFSIZ, "%s:%d:in `%s'", n->nd_file, nd_line(n), rb_id2name(frame->prev->last_func)); } else { snprintf(buf, BUFSIZ, "%s:%d", n->nd_file, nd_line(n)); } rb_ary_push(ary, rb_str_new2(buf)); } return ary; } /* * call-seq: * caller(start=1) => array * * Returns the current execution stack---an array containing strings in * the form ``file:line'' or ``file:line: in * `method'''. The optional _start_ parameter * determines the number of initial stack entries to omit from the * result. * * def a(skip) * caller(skip) * end * def b(skip) * a(skip) * end * def c(skip) * b(skip) * end * c(0) #=> ["prog:2:in `a'", "prog:5:in `b'", "prog:8:in `c'", "prog:10"] * c(1) #=> ["prog:5:in `b'", "prog:8:in `c'", "prog:11"] * c(2) #=> ["prog:8:in `c'", "prog:12"] * c(3) #=> ["prog:13"] */ static VALUE rb_f_caller(argc, argv) int argc; VALUE *argv; { VALUE level; int lev; rb_scan_args(argc, argv, "01", &level); if (NIL_P(level)) lev = 1; else lev = NUM2INT(level); if (lev < 0) rb_raise(rb_eArgError, "negative level (%d)", lev); return backtrace(lev); } void rb_backtrace() { long i; VALUE ary; ary = backtrace(-1); for (i=0; ilen; i++) { printf("\tfrom %s\n", RSTRING(RARRAY(ary)->ptr[i])->ptr); } } static VALUE make_backtrace() { return backtrace(-1); } ID rb_frame_last_func() { return ruby_frame->last_func; } static NODE* compile(src, file, line) VALUE src; char *file; int line; { NODE *node; int critical; ruby_nerrs = 0; StringValue(src); critical = rb_thread_critical; rb_thread_critical = Qtrue; node = rb_compile_string(file, src, line); rb_thread_critical = critical; if (ruby_nerrs == 0) return node; return 0; } static VALUE eval(self, src, scope, file, line) VALUE self, src, scope; char *file; int line; { struct BLOCK *data = NULL; volatile VALUE result = Qnil; struct SCOPE * volatile old_scope; struct BLOCK * volatile old_block; struct RVarmap * volatile old_dyna_vars; VALUE volatile old_cref; int volatile old_vmode; volatile VALUE old_wrapper; struct FRAME frame; NODE *nodesave = ruby_current_node; volatile int iter = ruby_frame->iter; volatile int safe = ruby_safe_level; int state; if (!NIL_P(scope)) { if (!rb_obj_is_proc(scope)) { rb_raise(rb_eTypeError, "wrong argument type %s (expected Proc/Binding)", rb_obj_classname(scope)); } Data_Get_Struct(scope, struct BLOCK, data); /* PUSH BLOCK from data */ frame = data->frame; frame.tmp = ruby_frame; /* gc protection */ ruby_frame = &(frame); old_scope = ruby_scope; ruby_scope = data->scope; old_block = ruby_block; ruby_block = data->prev; old_dyna_vars = ruby_dyna_vars; ruby_dyna_vars = data->dyna_vars; old_vmode = scope_vmode; scope_vmode = data->vmode; old_cref = (VALUE)ruby_cref; ruby_cref = data->cref; old_wrapper = ruby_wrapper; ruby_wrapper = data->wrapper; if ((file == 0 || (line == 1 && strcmp(file, "(eval)") == 0)) && data->frame.node) { file = data->frame.node->nd_file; if (!file) file = "__builtin__"; line = nd_line(data->frame.node); } self = data->self; ruby_frame->iter = data->iter; } else { if (ruby_frame->prev) { ruby_frame->iter = ruby_frame->prev->iter; } } if (file == 0) { ruby_set_current_source(); file = ruby_sourcefile; line = ruby_sourceline; } PUSH_CLASS(data ? data->klass : ruby_class); ruby_in_eval++; if (TYPE(ruby_class) == T_ICLASS) { ruby_class = RBASIC(ruby_class)->klass; } PUSH_TAG(PROT_NONE); if ((state = EXEC_TAG()) == 0) { NODE *node; ruby_safe_level = 0; result = ruby_errinfo; ruby_errinfo = Qnil; node = compile(src, file, line); ruby_safe_level = safe; if (ruby_nerrs > 0) { compile_error(0); } if (!NIL_P(result)) ruby_errinfo = result; result = eval_node(self, node); } POP_TAG(); POP_CLASS(); ruby_in_eval--; if (!NIL_P(scope)) { int dont_recycle = ruby_scope->flags & SCOPE_DONT_RECYCLE; ruby_wrapper = old_wrapper; ruby_cref = (NODE*)old_cref; ruby_frame = frame.tmp; ruby_scope = old_scope; ruby_block = old_block; ruby_dyna_vars = old_dyna_vars; data->vmode = scope_vmode; /* write back visibility mode */ scope_vmode = old_vmode; if (dont_recycle) { struct tag *tag; struct RVarmap *vars; scope_dup(ruby_scope); for (tag=prot_tag; tag; tag=tag->prev) { scope_dup(tag->scope); } for (vars = ruby_dyna_vars; vars; vars = vars->next) { FL_SET(vars, DVAR_DONT_RECYCLE); } } } else { ruby_frame->iter = iter; } ruby_current_node = nodesave; ruby_set_current_source(); if (state) { if (state == TAG_RAISE) { if (strcmp(file, "(eval)") == 0) { VALUE mesg, errat, bt2; errat = get_backtrace(ruby_errinfo); mesg = rb_attr_get(ruby_errinfo, rb_intern("mesg")); if (!NIL_P(errat) && TYPE(errat) == T_ARRAY && (bt2 = backtrace(-2), RARRAY(bt2)->len > 0)) { if (!NIL_P(mesg) && TYPE(mesg) == T_STRING) { rb_str_update(mesg, 0, 0, rb_str_new2(": ")); rb_str_update(mesg, 0, 0, RARRAY(errat)->ptr[0]); } RARRAY(errat)->ptr[0] = RARRAY(bt2)->ptr[0]; } } rb_exc_raise(ruby_errinfo); } JUMP_TAG(state); } return result; } /* * call-seq: * eval(string [, binding [, filename [,lineno]]]) => obj * * Evaluates the Ruby expression(s) in string. If * binding is given, the evaluation is performed in its * context. The binding may be a Binding object or a * Proc object. If the optional filename and * lineno parameters are present, they will be used when * reporting syntax errors. * * def getBinding(str) * return binding * end * str = "hello" * eval "str + ' Fred'" #=> "hello Fred" * eval "str + ' Fred'", getBinding("bye") #=> "bye Fred" */ static VALUE rb_f_eval(argc, argv, self) int argc; VALUE *argv; VALUE self; { VALUE src, scope, vfile, vline; char *file = "(eval)"; int line = 1; rb_scan_args(argc, argv, "13", &src, &scope, &vfile, &vline); if (ruby_safe_level >= 4) { StringValue(src); if (!NIL_P(scope) && !OBJ_TAINTED(scope)) { rb_raise(rb_eSecurityError, "Insecure: can't modify trusted binding"); } } else { SafeStringValue(src); } if (argc >= 3) { StringValue(vfile); } if (argc >= 4) { line = NUM2INT(vline); } if (!NIL_P(vfile)) file = RSTRING(vfile)->ptr; if (NIL_P(scope) && ruby_frame->prev) { struct FRAME *prev; VALUE val; prev = ruby_frame; PUSH_FRAME(); *ruby_frame = *prev->prev; ruby_frame->prev = prev; val = eval(self, src, scope, file, line); POP_FRAME(); return val; } return eval(self, src, scope, file, line); } /* function to call func under the specified class/module context */ static VALUE exec_under(func, under, cbase, args) VALUE (*func)(); VALUE under, cbase; void *args; { VALUE val = Qnil; /* OK */ int state; int mode; struct FRAME *f = ruby_frame; PUSH_CLASS(under); PUSH_FRAME(); ruby_frame->self = f->self; ruby_frame->last_func = f->last_func; ruby_frame->orig_func = f->orig_func; ruby_frame->last_class = f->last_class; ruby_frame->argc = f->argc; if (cbase) { PUSH_CREF(cbase); } mode = scope_vmode; SCOPE_SET(SCOPE_PUBLIC); PUSH_TAG(PROT_NONE); if ((state = EXEC_TAG()) == 0) { val = (*func)(args); } POP_TAG(); if (cbase) POP_CREF(); SCOPE_SET(mode); POP_FRAME(); POP_CLASS(); if (state) JUMP_TAG(state); return val; } static VALUE eval_under_i(args) VALUE *args; { struct FRAME *f = ruby_frame; if (f && (f = f->prev) && (f = f->prev)) { ruby_frame = f; } return eval(args[0], args[1], Qnil, (char*)args[2], (int)args[3]); } /* string eval under the class/module context */ static VALUE eval_under(under, self, src, file, line) VALUE under, self, src; const char *file; int line; { VALUE args[4]; if (ruby_safe_level >= 4) { StringValue(src); } else { SafeStringValue(src); } args[0] = self; args[1] = src; args[2] = (VALUE)file; args[3] = (VALUE)line; return exec_under(eval_under_i, under, under, args); } static VALUE yield_under_i(self) VALUE self; { return rb_yield_0(self, self, ruby_class, YIELD_PUBLIC_DEF, Qfalse); } /* block eval under the class/module context */ static VALUE yield_under(under, self) VALUE under, self; { return exec_under(yield_under_i, under, 0, self); } static VALUE specific_eval(argc, argv, klass, self) int argc; VALUE *argv; VALUE klass, self; { if (rb_block_given_p()) { if (argc > 0) { rb_raise(rb_eArgError, "wrong number of arguments (%d for 0)", argc); } return yield_under(klass, self); } else { char *file = "(eval)"; int line = 1; if (argc == 0) { rb_raise(rb_eArgError, "block not supplied"); } else { if (ruby_safe_level >= 4) { StringValue(argv[0]); } else { SafeStringValue(argv[0]); } if (argc > 3) { rb_raise(rb_eArgError, "wrong number of arguments: %s(src) or %s{..}", rb_id2name(ruby_frame->last_func), rb_id2name(ruby_frame->last_func)); } if (argc > 2) line = NUM2INT(argv[2]); if (argc > 1) { file = StringValuePtr(argv[1]); } } return eval_under(klass, self, argv[0], file, line); } } /* * call-seq: * obj.instance_eval(string [, filename [, lineno]] ) => obj * obj.instance_eval {| | block } => obj * * Evaluates a string containing Ruby source code, or the given block, * within the context of the receiver (_obj_). In order to set the * context, the variable +self+ is set to _obj_ while * the code is executing, giving the code access to _obj_'s * instance variables. In the version of instance_eval * that takes a +String+, the optional second and third * parameters supply a filename and starting line number that are used * when reporting compilation errors. * * class Klass * def initialize * @secret = 99 * end * end * k = Klass.new * k.instance_eval { @secret } #=> 99 */ VALUE rb_obj_instance_eval(argc, argv, self) int argc; VALUE *argv; VALUE self; { VALUE klass; if (SPECIAL_CONST_P(self)) { klass = Qnil; } else { klass = rb_singleton_class(self); } return specific_eval(argc, argv, klass, self); } /* * call-seq: * mod.class_eval(string [, filename [, lineno]]) => obj * mod.module_eval {|| block } => obj * * Evaluates the string or block in the context of _mod_. This can * be used to add methods to a class. module_eval returns * the result of evaluating its argument. The optional _filename_ * and _lineno_ parameters set the text for error messages. * * class Thing * end * a = %q{def hello() "Hello there!" end} * Thing.module_eval(a) * puts Thing.new.hello() * Thing.module_eval("invalid code", "dummy", 123) * * produces: * * Hello there! * dummy:123:in `module_eval': undefined local variable * or method `code' for Thing:Class */ VALUE rb_mod_module_eval(argc, argv, mod) int argc; VALUE *argv; VALUE mod; { return specific_eval(argc, argv, mod, mod); } VALUE rb_load_path; NORETURN(static void load_failed _((VALUE))); void rb_load(fname, wrap) VALUE fname; int wrap; { VALUE tmp; int state; volatile int prohibit_int = rb_prohibit_interrupt; volatile ID last_func; volatile VALUE wrapper = ruby_wrapper; volatile VALUE self = ruby_top_self; NODE *volatile last_node; NODE *saved_cref = ruby_cref; if (wrap && ruby_safe_level >= 4) { StringValue(fname); } else { SafeStringValue(fname); } fname = rb_str_new4(fname); tmp = rb_find_file(fname); if (!tmp) { load_failed(fname); } fname = tmp; ruby_errinfo = Qnil; /* ensure */ PUSH_VARS(); PUSH_CLASS(ruby_wrapper); ruby_cref = ruby_top_cref; if (!wrap) { rb_secure(4); /* should alter global state */ ruby_class = rb_cObject; ruby_wrapper = 0; } else { /* load in anonymous module as toplevel */ ruby_class = ruby_wrapper = rb_module_new(); self = rb_obj_clone(ruby_top_self); rb_extend_object(self, ruby_wrapper); PUSH_CREF(ruby_wrapper); } PUSH_ITER(ITER_NOT); PUSH_FRAME(); ruby_frame->last_func = 0; ruby_frame->last_class = 0; ruby_frame->self = self; PUSH_SCOPE(); /* default visibility is private at loading toplevel */ SCOPE_SET(SCOPE_PRIVATE); PUSH_TAG(PROT_NONE); state = EXEC_TAG(); last_func = ruby_frame->last_func; last_node = ruby_current_node; if (!ruby_current_node && ruby_sourcefile) { last_node = NEW_NEWLINE(0); } ruby_current_node = 0; if (state == 0) { NODE *node; volatile int critical; DEFER_INTS; ruby_in_eval++; critical = rb_thread_critical; rb_thread_critical = Qtrue; rb_load_file(RSTRING(fname)->ptr); ruby_in_eval--; node = ruby_eval_tree; rb_thread_critical = critical; ALLOW_INTS; if (ruby_nerrs == 0) { eval_node(self, node); } } ruby_frame->last_func = last_func; ruby_current_node = last_node; ruby_sourcefile = 0; ruby_set_current_source(); if (ruby_scope->flags == SCOPE_ALLOCA && ruby_class == rb_cObject) { if (ruby_scope->local_tbl) /* toplevel was empty */ free(ruby_scope->local_tbl); } POP_TAG(); rb_prohibit_interrupt = prohibit_int; ruby_cref = saved_cref; POP_SCOPE(); POP_FRAME(); POP_ITER(); POP_CLASS(); POP_VARS(); ruby_wrapper = wrapper; if (ruby_nerrs > 0) { ruby_nerrs = 0; rb_exc_raise(ruby_errinfo); } if (state) jump_tag_but_local_jump(state, Qundef); if (!NIL_P(ruby_errinfo)) /* exception during load */ rb_exc_raise(ruby_errinfo); } void rb_load_protect(fname, wrap, state) VALUE fname; int wrap; int *state; { int status; PUSH_TAG(PROT_NONE); if ((status = EXEC_TAG()) == 0) { rb_load(fname, wrap); } POP_TAG(); if (state) *state = status; } /* * call-seq: * load(filename, wrap=false) => true * * Loads and executes the Ruby * program in the file _filename_. If the filename does not * resolve to an absolute path, the file is searched for in the library * directories listed in $:. If the optional _wrap_ * parameter is +true+, the loaded script will be executed * under an anonymous module, protecting the calling program's global * namespace. In no circumstance will any local variables in the loaded * file be propagated to the loading environment. */ static VALUE rb_f_load(argc, argv) int argc; VALUE *argv; { VALUE fname, wrap; rb_scan_args(argc, argv, "11", &fname, &wrap); rb_load(fname, RTEST(wrap)); return Qtrue; } VALUE ruby_dln_librefs; static VALUE rb_features; static st_table *loading_tbl; #define IS_SOEXT(e) (strcmp(e, ".so") == 0 || strcmp(e, ".o") == 0) #ifdef DLEXT2 #define IS_DLEXT(e) (strcmp(e, DLEXT) == 0 || strcmp(e, DLEXT2) == 0) #else #define IS_DLEXT(e) (strcmp(e, DLEXT) == 0) #endif static const char *const loadable_ext[] = { ".rb", DLEXT, #ifdef DLEXT2 DLEXT2, #endif 0 }; static int rb_feature_p _((const char **, const char *, int)); static int search_required _((VALUE, VALUE *, VALUE *)); static int rb_feature_p(ftptr, ext, rb) const char **ftptr, *ext; int rb; { VALUE v; const char *f, *e, *feature = *ftptr; long i, len, elen; if (ext) { len = ext - feature; elen = strlen(ext); } else { len = strlen(feature); elen = 0; } for (i = 0; i < RARRAY_LEN(rb_features); ++i) { v = RARRAY_PTR(rb_features)[i]; f = StringValuePtr(v); if (RSTRING_LEN(v) < len || strncmp(f, feature, len) != 0) continue; if (!*(e = f + len)) { if (ext) continue; *ftptr = 0; return 'u'; } if (*e != '.') continue; if ((!rb || !ext) && (IS_SOEXT(e) || IS_DLEXT(e))) { *ftptr = 0; return 's'; } if ((rb || !ext) && (strcmp(e, ".rb") == 0)) { *ftptr = 0; return 'r'; } } if (loading_tbl) { if (st_lookup(loading_tbl, (st_data_t)feature, (st_data_t *)ftptr)) { if (!ext) return 'u'; return strcmp(ext, ".rb") ? 's' : 'r'; } else { char *buf; if (ext && *ext) return 0; buf = ALLOCA_N(char, len + DLEXT_MAXLEN + 1); MEMCPY(buf, feature, char, len); for (i = 0; (e = loadable_ext[i]) != 0; i++) { strncpy(buf + len, e, DLEXT_MAXLEN + 1); if (st_lookup(loading_tbl, (st_data_t)buf, (st_data_t *)ftptr)) { return i ? 's' : 'r'; } } } } return 0; } #define rb_feature_p(feature, ext, rb) rb_feature_p(&feature, ext, rb) int rb_provided(feature) const char *feature; { const char *ext = strrchr(feature, '.'); if (ext && !strchr(ext, '/')) { if (strcmp(".rb", ext) == 0) { if (rb_feature_p(feature, ext, Qtrue)) return Qtrue; return Qfalse; } else if (IS_SOEXT(ext) || IS_DLEXT(ext)) { if (rb_feature_p(feature, ext, Qfalse)) return Qtrue; return Qfalse; } } if (rb_feature_p(feature, feature + strlen(feature), Qtrue)) return Qtrue; return Qfalse; } static void rb_provide_feature(feature) VALUE feature; { rb_ary_push(rb_features, feature); } void rb_provide(feature) const char *feature; { rb_provide_feature(rb_str_new2(feature)); } static char * load_lock(ftptr) const char *ftptr; { st_data_t th; if (!loading_tbl || !st_lookup(loading_tbl, (st_data_t)ftptr, &th)) { /* loading ruby library should be serialized. */ if (!loading_tbl) { loading_tbl = st_init_strtable(); } /* partial state */ ftptr = ruby_strdup(ftptr); st_insert(loading_tbl, (st_data_t)ftptr, (st_data_t)curr_thread); return (char *)ftptr; } do { rb_thread_t owner = (rb_thread_t)th; if (owner == curr_thread) return 0; rb_thread_join(owner->thread, -1.0); } while (st_lookup(loading_tbl, (st_data_t)ftptr, &th)); return 0; } static void load_unlock(const char *ftptr) { if (ftptr) { st_data_t key = (st_data_t)ftptr; if (st_delete(loading_tbl, &key, 0)) { free((char *)key); } } } /* * call-seq: * require(string) => true or false * * Ruby tries to load the library named _string_, returning * +true+ if successful. If the filename does not resolve to * an absolute path, it will be searched for in the directories listed * in $:. If the file has the extension ``.rb'', it is * loaded as a source file; if the extension is ``.so'', ``.o'', or * ``.dll'', or whatever the default shared library extension is on * the current platform, Ruby loads the shared library as a Ruby * extension. Otherwise, Ruby tries adding ``.rb'', ``.so'', and so on * to the name. The name of the loaded feature is added to the array in * $". A feature will not be loaded if it's name already * appears in $". However, the file name is not converted * to an absolute path, so that ``require 'a';require * './a''' will load a.rb twice. * * require "my-library.rb" * require "db-driver" */ VALUE rb_f_require(obj, fname) VALUE obj, fname; { return rb_require_safe(fname, ruby_safe_level); } static int search_required(fname, featurep, path) VALUE fname, *featurep, *path; { VALUE tmp; const char *ext, *ftptr; int type; *featurep = fname; *path = 0; ext = strrchr(ftptr = RSTRING_PTR(fname), '.'); if (ext && !strchr(ext, '/')) { if (strcmp(".rb", ext) == 0) { if (rb_feature_p(ftptr, ext, Qtrue)) { if (ftptr) *path = rb_str_new2(ftptr); return 'r'; } if ((*path = rb_find_file(fname)) != 0) return 'r'; return 0; } else if (IS_SOEXT(ext)) { if (rb_feature_p(ftptr, ext, Qfalse)) { if (ftptr) *path = rb_str_new2(ftptr); return 's'; } tmp = rb_str_new(RSTRING_PTR(fname), ext-RSTRING_PTR(fname)); *featurep = tmp; #ifdef DLEXT2 OBJ_FREEZE(tmp); if (rb_find_file_ext(&tmp, loadable_ext+1)) { *featurep = tmp; *path = rb_find_file(tmp); return 's'; } #else rb_str_cat2(tmp, DLEXT); OBJ_FREEZE(tmp); if ((*path = rb_find_file(tmp)) != 0) { return 's'; } #endif } else if (IS_DLEXT(ext)) { if (rb_feature_p(ftptr, ext, Qfalse)) { if (ftptr) *path = rb_str_new2(ftptr); return 's'; } if ((*path = rb_find_file(fname)) != 0) return 's'; } } tmp = fname; type = rb_find_file_ext(&tmp, loadable_ext); *featurep = tmp; switch (type) { case 0: type = rb_feature_p(ftptr, 0, Qfalse); if (type && ftptr) *path = rb_str_new2(ftptr); return type; default: ext = strrchr(ftptr = RSTRING(tmp)->ptr, '.'); if (!rb_feature_p(ftptr, ext, !--type)) *path = rb_find_file(tmp); else if (ftptr) *path = rb_str_new2(ftptr); } return type ? 's' : 'r'; } static void load_failed(fname) VALUE fname; { rb_raise(rb_eLoadError, "no such file to load -- %s", RSTRING(fname)->ptr); } VALUE rb_require_safe(fname, safe) VALUE fname; int safe; { VALUE result = Qnil; volatile VALUE errinfo = ruby_errinfo; int state; struct { NODE *node; ID func; int vmode, safe; } volatile saved; char *volatile ftptr = 0; if (OBJ_TAINTED(fname)) { rb_check_safe_obj(fname); } StringValue(fname); fname = rb_str_new4(fname); saved.vmode = scope_vmode; saved.node = ruby_current_node; saved.func = ruby_frame->last_func; saved.safe = ruby_safe_level; PUSH_TAG(PROT_NONE); if ((state = EXEC_TAG()) == 0) { VALUE feature, path; long handle; int found; ruby_safe_level = safe; found = search_required(fname, &feature, &path); if (found) { if (!path || !(ftptr = load_lock(RSTRING_PTR(feature)))) { result = Qfalse; } else { ruby_safe_level = 0; switch (found) { case 'r': rb_load(path, 0); break; case 's': ruby_current_node = 0; ruby_sourcefile = rb_source_filename(RSTRING(path)->ptr); ruby_sourceline = 0; ruby_frame->last_func = 0; SCOPE_SET(SCOPE_PUBLIC); handle = (long)dln_load(RSTRING(path)->ptr); rb_ary_push(ruby_dln_librefs, LONG2NUM(handle)); break; } rb_provide_feature(feature); result = Qtrue; } } } POP_TAG(); ruby_current_node = saved.node; ruby_set_current_source(); ruby_frame->last_func = saved.func; SCOPE_SET(saved.vmode); ruby_safe_level = saved.safe; load_unlock(ftptr); if (state) JUMP_TAG(state); if (NIL_P(result)) { load_failed(fname); } ruby_errinfo = errinfo; return result; } VALUE rb_require(fname) const char *fname; { VALUE fn = rb_str_new2(fname); OBJ_FREEZE(fn); return rb_require_safe(fn, ruby_safe_level); } void ruby_init_ext(name, init) const char *name; void (*init) _((void)); { ruby_current_node = 0; ruby_sourcefile = rb_source_filename(name); ruby_sourceline = 0; ruby_frame->last_func = 0; ruby_frame->orig_func = 0; SCOPE_SET(SCOPE_PUBLIC); if (load_lock(name)) { (*init)(); rb_provide(name); load_unlock(name); } } static void secure_visibility(self) VALUE self; { if (ruby_safe_level >= 4 && !OBJ_TAINTED(self)) { rb_raise(rb_eSecurityError, "Insecure: can't change method visibility"); } } static void set_method_visibility(self, argc, argv, ex) VALUE self; int argc; VALUE *argv; ID ex; { int i; secure_visibility(self); for (i=0; i self * public(symbol, ...) => self * * With no arguments, sets the default visibility for subsequently * defined methods to public. With arguments, sets the named methods to * have public visibility. */ static VALUE rb_mod_public(argc, argv, module) int argc; VALUE *argv; VALUE module; { secure_visibility(module); if (argc == 0) { SCOPE_SET(SCOPE_PUBLIC); } else { set_method_visibility(module, argc, argv, NOEX_PUBLIC); } return module; } /* * call-seq: * protected => self * protected(symbol, ...) => self * * With no arguments, sets the default visibility for subsequently * defined methods to protected. With arguments, sets the named methods * to have protected visibility. */ static VALUE rb_mod_protected(argc, argv, module) int argc; VALUE *argv; VALUE module; { secure_visibility(module); if (argc == 0) { SCOPE_SET(SCOPE_PROTECTED); } else { set_method_visibility(module, argc, argv, NOEX_PROTECTED); } return module; } /* * call-seq: * private => self * private(symbol, ...) => self * * With no arguments, sets the default visibility for subsequently * defined methods to private. With arguments, sets the named methods * to have private visibility. * * module Mod * def a() end * def b() end * private * def c() end * private :a * end * Mod.private_instance_methods #=> ["a", "c"] */ static VALUE rb_mod_private(argc, argv, module) int argc; VALUE *argv; VALUE module; { secure_visibility(module); if (argc == 0) { SCOPE_SET(SCOPE_PRIVATE); } else { set_method_visibility(module, argc, argv, NOEX_PRIVATE); } return module; } /* * call-seq: * mod.public_class_method(symbol, ...) => mod * * Makes a list of existing class methods public. */ static VALUE rb_mod_public_method(argc, argv, obj) int argc; VALUE *argv; VALUE obj; { set_method_visibility(CLASS_OF(obj), argc, argv, NOEX_PUBLIC); return obj; } /* * call-seq: * mod.private_class_method(symbol, ...) => mod * * Makes existing class methods private. Often used to hide the default * constructor new. * * class SimpleSingleton # Not thread safe * private_class_method :new * def SimpleSingleton.create(*args, &block) * @me = new(*args, &block) if ! @me * @me * end * end */ static VALUE rb_mod_private_method(argc, argv, obj) int argc; VALUE *argv; VALUE obj; { set_method_visibility(CLASS_OF(obj), argc, argv, NOEX_PRIVATE); return obj; } /* * call-seq: * public * public(symbol, ...) * * With no arguments, sets the default visibility for subsequently * defined methods to public. With arguments, sets the named methods to * have public visibility. */ static VALUE top_public(argc, argv) int argc; VALUE *argv; { return rb_mod_public(argc, argv, rb_cObject); } static VALUE top_private(argc, argv) int argc; VALUE *argv; { return rb_mod_private(argc, argv, rb_cObject); } /* * call-seq: * module_function(symbol, ...) => self * * Creates module functions for the named methods. These functions may * be called with the module as a receiver, and also become available * as instance methods to classes that mix in the module. Module * functions are copies of the original, and so may be changed * independently. The instance-method versions are made private. If * used with no arguments, subsequently defined methods become module * functions. * * module Mod * def one * "This is one" * end * module_function :one * end * class Cls * include Mod * def callOne * one * end * end * Mod.one #=> "This is one" * c = Cls.new * c.callOne #=> "This is one" * module Mod * def one * "This is the new one" * end * end * Mod.one #=> "This is one" * c.callOne #=> "This is the new one" */ static VALUE rb_mod_modfunc(argc, argv, module) int argc; VALUE *argv; VALUE module; { int i; ID id; NODE *body; if (TYPE(module) != T_MODULE) { rb_raise(rb_eTypeError, "module_function must be called for modules"); } secure_visibility(module); if (argc == 0) { SCOPE_SET(SCOPE_MODFUNC); return module; } set_method_visibility(module, argc, argv, NOEX_PRIVATE); for (i=0; ind_body == 0) { print_undef(module, id); } if (nd_type(body->nd_body) != NODE_ZSUPER) { break; /* normal case: need not to follow 'super' link */ } m = RCLASS(m)->super; if (!m) break; } rb_add_method(rb_singleton_class(module), id, body->nd_body, NOEX_PUBLIC); } return module; } /* * call-seq: * append_features(mod) => mod * * When this module is included in another, Ruby calls * append_features in this module, passing it the * receiving module in _mod_. Ruby's default implementation is * to add the constants, methods, and module variables of this module * to _mod_ if this module has not already been added to * _mod_ or one of its ancestors. See also Module#include. */ static VALUE rb_mod_append_features(module, include) VALUE module, include; { switch (TYPE(include)) { case T_CLASS: case T_MODULE: break; default: Check_Type(include, T_CLASS); break; } rb_include_module(include, module); return module; } /* * call-seq: * include(module, ...) => self * * Invokes Module.append_features on each parameter in turn. */ static VALUE rb_mod_include(argc, argv, module) int argc; VALUE *argv; VALUE module; { int i; for (i=0; i obj * * Extends the specified object by adding this module's constants and * methods (which are added as singleton methods). This is the callback * method used by Object#extend. * * module Picky * def Picky.extend_object(o) * if String === o * puts "Can't add Picky to a String" * else * puts "Picky added to #{o.class}" * super * end * end * end * (s = Array.new).extend Picky # Call Object.extend * (s = "quick brown fox").extend Picky * * produces: * * Picky added to Array * Can't add Picky to a String */ static VALUE rb_mod_extend_object(mod, obj) VALUE mod, obj; { rb_extend_object(obj, mod); return obj; } /* * call-seq: * obj.extend(module, ...) => obj * * Adds to _obj_ the instance methods from each module given as a * parameter. * * module Mod * def hello * "Hello from Mod.\n" * end * end * * class Klass * def hello * "Hello from Klass.\n" * end * end * * k = Klass.new * k.hello #=> "Hello from Klass.\n" * k.extend(Mod) #=> # * k.hello #=> "Hello from Mod.\n" */ static VALUE rb_obj_extend(argc, argv, obj) int argc; VALUE *argv; VALUE obj; { int i; if (argc == 0) { rb_raise(rb_eArgError, "wrong number of arguments (0 for 1)"); } for (i=0; i self * * Invokes Module.append_features * on each parameter in turn. Effectively adds the methods and constants * in each module to the receiver. */ static VALUE top_include(argc, argv, self) int argc; VALUE *argv; VALUE self; { rb_secure(4); if (ruby_wrapper) { rb_warning("main#include in the wrapped load is effective only in wrapper module"); return rb_mod_include(argc, argv, ruby_wrapper); } return rb_mod_include(argc, argv, rb_cObject); } VALUE rb_f_trace_var(); VALUE rb_f_untrace_var(); static void errinfo_setter(val, id, var) VALUE val; ID id; VALUE *var; { if (!NIL_P(val) && !rb_obj_is_kind_of(val, rb_eException)) { rb_raise(rb_eTypeError, "assigning non-exception to $!"); } *var = val; } static VALUE errat_getter(id) ID id; { return get_backtrace(ruby_errinfo); } static void errat_setter(val, id, var) VALUE val; ID id; VALUE *var; { if (NIL_P(ruby_errinfo)) { rb_raise(rb_eArgError, "$! not set"); } set_backtrace(ruby_errinfo, val); } /* * call-seq: * local_variables => array * * Returns the names of the current local variables. * * fred = 1 * for i in 1..10 * # ... * end * local_variables #=> ["fred", "i"] */ static VALUE rb_f_local_variables() { ID *tbl; int n, i; VALUE ary = rb_ary_new(); struct RVarmap *vars; tbl = ruby_scope->local_tbl; if (tbl) { n = *tbl++; for (i=2; iid && rb_is_local_id(vars->id)) { /* skip $_, $~ and flip states */ rb_ary_push(ary, rb_str_new2(rb_id2name(vars->id))); } vars = vars->next; } return ary; } static VALUE rb_f_catch _((VALUE,VALUE)); NORETURN(static VALUE rb_f_throw _((int,VALUE*))); struct end_proc_data { void (*func)(); VALUE data; int safe; struct end_proc_data *next; }; static struct end_proc_data *end_procs, *ephemeral_end_procs, *tmp_end_procs; void rb_set_end_proc(func, data) void (*func) _((VALUE)); VALUE data; { struct end_proc_data *link = ALLOC(struct end_proc_data); struct end_proc_data **list; if (ruby_wrapper) list = &ephemeral_end_procs; else list = &end_procs; link->next = *list; link->func = func; link->data = data; link->safe = ruby_safe_level; *list = link; } void rb_mark_end_proc() { struct end_proc_data *link; link = end_procs; while (link) { rb_gc_mark(link->data); link = link->next; } link = ephemeral_end_procs; while (link) { rb_gc_mark(link->data); link = link->next; } link = tmp_end_procs; while (link) { rb_gc_mark(link->data); link = link->next; } } static void call_end_proc _((VALUE data)); static void call_end_proc(data) VALUE data; { PUSH_ITER(ITER_NOT); PUSH_FRAME(); ruby_frame->self = ruby_frame->prev->self; ruby_frame->node = 0; ruby_frame->last_func = 0; ruby_frame->last_class = 0; proc_invoke(data, rb_ary_new2(0), Qundef, 0); POP_FRAME(); POP_ITER(); } static void rb_f_END() { PUSH_FRAME(); ruby_frame->argc = 0; ruby_frame->iter = ITER_CUR; rb_set_end_proc(call_end_proc, rb_block_proc()); POP_FRAME(); } /* * call-seq: * at_exit { block } -> proc * * Converts _block_ to a +Proc+ object (and therefore * binds it at the point of call) and registers it for execution when * the program exits. If multiple handlers are registered, they are * executed in reverse order of registration. * * def do_at_exit(str1) * at_exit { print str1 } * end * at_exit { puts "cruel world" } * do_at_exit("goodbye ") * exit * * produces: * * goodbye cruel world */ static VALUE rb_f_at_exit() { VALUE proc; if (!rb_block_given_p()) { rb_raise(rb_eArgError, "called without a block"); } proc = rb_block_proc(); rb_set_end_proc(call_end_proc, proc); return proc; } void rb_exec_end_proc() { struct end_proc_data *link, *tmp; int status; volatile int safe = ruby_safe_level; while (ephemeral_end_procs) { tmp_end_procs = link = ephemeral_end_procs; ephemeral_end_procs = 0; while (link) { PUSH_TAG(PROT_NONE); if ((status = EXEC_TAG()) == 0) { ruby_safe_level = link->safe; (*link->func)(link->data); } POP_TAG(); if (status) { error_handle(status); } tmp = link; tmp_end_procs = link = link->next; free(tmp); } } while (end_procs) { tmp_end_procs = link = end_procs; end_procs = 0; while (link) { PUSH_TAG(PROT_NONE); if ((status = EXEC_TAG()) == 0) { ruby_safe_level = link->safe; (*link->func)(link->data); } POP_TAG(); if (status) { error_handle(status); } tmp = link; tmp_end_procs = link = link->next; free(tmp); } } ruby_safe_level = safe; } void Init_eval() { init = rb_intern("initialize"); eqq = rb_intern("==="); each = rb_intern("each"); aref = rb_intern("[]"); aset = rb_intern("[]="); match = rb_intern("=~"); missing = rb_intern("method_missing"); added = rb_intern("method_added"); singleton_added = rb_intern("singleton_method_added"); removed = rb_intern("method_removed"); singleton_removed = rb_intern("singleton_method_removed"); undefined = rb_intern("method_undefined"); singleton_undefined = rb_intern("singleton_method_undefined"); __id__ = rb_intern("__id__"); __send__ = rb_intern("__send__"); rb_global_variable((void *)&top_scope); rb_global_variable((void *)&ruby_eval_tree_begin); rb_global_variable((void *)&ruby_eval_tree); rb_global_variable((void *)&ruby_dyna_vars); rb_define_virtual_variable("$@", errat_getter, errat_setter); rb_define_hooked_variable("$!", &ruby_errinfo, 0, errinfo_setter); rb_define_global_function("eval", rb_f_eval, -1); rb_define_global_function("iterator?", rb_f_block_given_p, 0); rb_define_global_function("block_given?", rb_f_block_given_p, 0); rb_define_global_function("method_missing", rb_method_missing, -1); rb_define_global_function("loop", rb_f_loop, 0); rb_define_method(rb_mKernel, "respond_to?", obj_respond_to, -1); respond_to = rb_intern("respond_to?"); rb_global_variable((void *)&basic_respond_to); basic_respond_to = rb_method_node(rb_cObject, respond_to); rb_define_global_function("raise", rb_f_raise, -1); rb_define_global_function("fail", rb_f_raise, -1); rb_define_global_function("caller", rb_f_caller, -1); rb_define_global_function("exit", rb_f_exit, -1); rb_define_global_function("abort", rb_f_abort, -1); rb_define_global_function("at_exit", rb_f_at_exit, 0); rb_define_global_function("catch", rb_f_catch, 1); rb_define_global_function("throw", rb_f_throw, -1); rb_define_global_function("global_variables", rb_f_global_variables, 0); /* in variable.c */ rb_define_global_function("local_variables", rb_f_local_variables, 0); rb_define_method(rb_mKernel, "send", rb_f_send, -1); rb_define_method(rb_mKernel, "__send__", rb_f_send, -1); rb_define_method(rb_mKernel, "instance_eval", rb_obj_instance_eval, -1); rb_define_private_method(rb_cModule, "append_features", rb_mod_append_features, 1); rb_define_private_method(rb_cModule, "extend_object", rb_mod_extend_object, 1); rb_define_private_method(rb_cModule, "include", rb_mod_include, -1); rb_define_private_method(rb_cModule, "public", rb_mod_public, -1); rb_define_private_method(rb_cModule, "protected", rb_mod_protected, -1); rb_define_private_method(rb_cModule, "private", rb_mod_private, -1); rb_define_private_method(rb_cModule, "module_function", rb_mod_modfunc, -1); rb_define_method(rb_cModule, "method_defined?", rb_mod_method_defined, 1); rb_define_method(rb_cModule, "public_method_defined?", rb_mod_public_method_defined, 1); rb_define_method(rb_cModule, "private_method_defined?", rb_mod_private_method_defined, 1); rb_define_method(rb_cModule, "protected_method_defined?", rb_mod_protected_method_defined, 1); rb_define_method(rb_cModule, "public_class_method", rb_mod_public_method, -1); rb_define_method(rb_cModule, "private_class_method", rb_mod_private_method, -1); rb_define_method(rb_cModule, "module_eval", rb_mod_module_eval, -1); rb_define_method(rb_cModule, "class_eval", rb_mod_module_eval, -1); rb_undef_method(rb_cClass, "module_function"); rb_define_private_method(rb_cModule, "remove_method", rb_mod_remove_method, -1); rb_define_private_method(rb_cModule, "undef_method", rb_mod_undef_method, -1); rb_define_private_method(rb_cModule, "alias_method", rb_mod_alias_method, 2); rb_define_private_method(rb_cModule, "define_method", rb_mod_define_method, -1); rb_define_singleton_method(rb_cModule, "nesting", rb_mod_nesting, 0); rb_define_singleton_method(rb_cModule, "constants", rb_mod_s_constants, 0); rb_define_singleton_method(ruby_top_self, "include", top_include, -1); rb_define_singleton_method(ruby_top_self, "public", top_public, -1); rb_define_singleton_method(ruby_top_self, "private", top_private, -1); rb_define_method(rb_mKernel, "extend", rb_obj_extend, -1); rb_define_global_function("trace_var", rb_f_trace_var, -1); /* in variable.c */ rb_define_global_function("untrace_var", rb_f_untrace_var, -1); /* in variable.c */ rb_define_global_function("set_trace_func", set_trace_func, 1); rb_global_variable(&trace_func); rb_define_virtual_variable("$SAFE", safe_getter, safe_setter); } /* * call-seq: * mod.autoload(name, filename) => nil * * Registers _filename_ to be loaded (using Kernel::require) * the first time that _name_ (which may be a String or * a symbol) is accessed in the namespace of _mod_. * * module A * end * A.autoload(:B, "b") * A::B.doit # autoloads "b" */ static VALUE rb_mod_autoload(mod, sym, file) VALUE mod; VALUE sym; VALUE file; { ID id = rb_to_id(sym); Check_SafeStr(file); rb_autoload(mod, id, RSTRING(file)->ptr); return Qnil; } /* * call-seq: * mod.autoload?(name) => String or nil * * Returns _filename_ to be loaded if _name_ is registered as * +autoload+ in the namespace of _mod_. * * module A * end * A.autoload(:B, "b") * A.autoload?(:B) # => "b" */ static VALUE rb_mod_autoload_p(mod, sym) VALUE mod, sym; { return rb_autoload_p(mod, rb_to_id(sym)); } /* * call-seq: * autoload(module, filename) => nil * * Registers _filename_ to be loaded (using Kernel::require) * the first time that _module_ (which may be a String or * a symbol) is accessed. * * autoload(:MyModule, "/usr/local/lib/modules/my_module.rb") */ static VALUE rb_f_autoload(obj, sym, file) VALUE obj; VALUE sym; VALUE file; { if (NIL_P(ruby_cbase)) { rb_raise(rb_eTypeError, "no class/module for autoload target"); } return rb_mod_autoload(ruby_cbase, sym, file); } /* * call-seq: * autoload(module, filename) => nil * * Registers _filename_ to be loaded (using Kernel::require) * the first time that _module_ (which may be a String or * a symbol) is accessed. * * autoload(:MyModule, "/usr/local/lib/modules/my_module.rb") */ static VALUE rb_f_autoload_p(obj, sym) VALUE obj; VALUE sym; { /* use ruby_cbase as same as rb_f_autoload. */ if (NIL_P(ruby_cbase)) { return Qfalse; } return rb_mod_autoload_p(ruby_cbase, sym); } void Init_load() { rb_define_readonly_variable("$:", &rb_load_path); rb_define_readonly_variable("$-I", &rb_load_path); rb_define_readonly_variable("$LOAD_PATH", &rb_load_path); rb_load_path = rb_ary_new(); rb_define_readonly_variable("$\"", &rb_features); rb_define_readonly_variable("$LOADED_FEATURES", &rb_features); rb_features = rb_ary_new(); rb_define_global_function("load", rb_f_load, -1); rb_define_global_function("require", rb_f_require, 1); rb_define_method(rb_cModule, "autoload", rb_mod_autoload, 2); rb_define_method(rb_cModule, "autoload?", rb_mod_autoload_p, 1); rb_define_global_function("autoload", rb_f_autoload, 2); rb_define_global_function("autoload?", rb_f_autoload_p, 1); rb_global_variable(&ruby_wrapper); rb_global_variable(&ruby_dln_librefs); ruby_dln_librefs = rb_ary_new(); } static void scope_dup(scope) struct SCOPE *scope; { ID *tbl; VALUE *vars; scope->flags |= SCOPE_DONT_RECYCLE; if (scope->flags & SCOPE_MALLOC) return; if (scope->local_tbl) { tbl = scope->local_tbl; vars = ALLOC_N(VALUE, tbl[0]+1); *vars++ = scope->local_vars[-1]; MEMCPY(vars, scope->local_vars, VALUE, tbl[0]); scope->local_vars = vars; scope->flags |= SCOPE_MALLOC; } } static void blk_mark(data) struct BLOCK *data; { while (data) { rb_gc_mark_frame(&data->frame); rb_gc_mark((VALUE)data->scope); rb_gc_mark((VALUE)data->var); rb_gc_mark((VALUE)data->body); rb_gc_mark((VALUE)data->self); rb_gc_mark((VALUE)data->dyna_vars); rb_gc_mark((VALUE)data->cref); rb_gc_mark(data->wrapper); rb_gc_mark(data->block_obj); data = data->prev; } } static void frame_free(frame) struct FRAME *frame; { struct FRAME *tmp; frame = frame->prev; while (frame) { tmp = frame; frame = frame->prev; free(tmp); } } static void blk_free(data) struct BLOCK *data; { void *tmp; while (data) { frame_free(&data->frame); tmp = data; data = data->prev; free(tmp); } } static void frame_dup(frame) struct FRAME *frame; { struct FRAME *tmp; for (;;) { frame->tmp = 0; /* should not preserve tmp */ if (!frame->prev) break; tmp = ALLOC(struct FRAME); *tmp = *frame->prev; frame->prev = tmp; frame = tmp; } } static void blk_copy_prev(block) struct BLOCK *block; { struct BLOCK *tmp; struct RVarmap* vars; while (block->prev) { tmp = ALLOC_N(struct BLOCK, 1); MEMCPY(tmp, block->prev, struct BLOCK, 1); scope_dup(tmp->scope); frame_dup(&tmp->frame); for (vars = tmp->dyna_vars; vars; vars = vars->next) { if (FL_TEST(vars, DVAR_DONT_RECYCLE)) break; FL_SET(vars, DVAR_DONT_RECYCLE); } block->prev = tmp; block = tmp; } } static void blk_dup(dup, orig) struct BLOCK *dup, *orig; { MEMCPY(dup, orig, struct BLOCK, 1); frame_dup(&dup->frame); if (dup->iter) { blk_copy_prev(dup); } else { dup->prev = 0; } } /* * MISSING: documentation */ static VALUE proc_clone(self) VALUE self; { struct BLOCK *orig, *data; VALUE bind; Data_Get_Struct(self, struct BLOCK, orig); bind = Data_Make_Struct(rb_obj_class(self),struct BLOCK,blk_mark,blk_free,data); CLONESETUP(bind, self); blk_dup(data, orig); return bind; } /* * MISSING: documentation */ #define PROC_TSHIFT (FL_USHIFT+1) #define PROC_TMASK (FL_USER1|FL_USER2|FL_USER3) #define PROC_TMAX (PROC_TMASK >> PROC_TSHIFT) static int proc_get_safe_level(VALUE); static VALUE proc_dup(self) VALUE self; { struct BLOCK *orig, *data; VALUE bind; int safe = proc_get_safe_level(self); Data_Get_Struct(self, struct BLOCK, orig); bind = Data_Make_Struct(rb_obj_class(self),struct BLOCK,blk_mark,blk_free,data); blk_dup(data, orig); if (safe > PROC_TMAX) safe = PROC_TMAX; FL_SET(bind, (safe << PROC_TSHIFT) & PROC_TMASK); return bind; } /* * call-seq: * binding -> a_binding * * Returns a +Binding+ object, describing the variable and * method bindings at the point of call. This object can be used when * calling +eval+ to execute the evaluated command in this * environment. Also see the description of class +Binding+. * * def getBinding(param) * return binding * end * b = getBinding("hello") * eval("param", b) #=> "hello" */ static VALUE rb_f_binding(self) VALUE self; { struct BLOCK *data, *p; struct RVarmap *vars; VALUE bind; PUSH_BLOCK(0,0); bind = Data_Make_Struct(rb_cBinding,struct BLOCK,blk_mark,blk_free,data); *data = *ruby_block; data->orig_thread = rb_thread_current(); data->wrapper = ruby_wrapper; data->iter = rb_f_block_given_p(); frame_dup(&data->frame); if (ruby_frame->prev) { data->frame.last_func = ruby_frame->prev->last_func; data->frame.last_class = ruby_frame->prev->last_class; data->frame.orig_func = ruby_frame->prev->orig_func; } if (data->iter) { blk_copy_prev(data); } else { data->prev = 0; } for (p = data; p; p = p->prev) { for (vars = p->dyna_vars; vars; vars = vars->next) { if (FL_TEST(vars, DVAR_DONT_RECYCLE)) break; FL_SET(vars, DVAR_DONT_RECYCLE); } } scope_dup(data->scope); POP_BLOCK(); return bind; } #define SAFE_LEVEL_MAX PROC_TMASK static void proc_save_safe_level(data) VALUE data; { int safe = ruby_safe_level; if (safe > PROC_TMAX) safe = PROC_TMAX; FL_SET(data, (safe << PROC_TSHIFT) & PROC_TMASK); } static int proc_get_safe_level(data) VALUE data; { return (RBASIC(data)->flags & PROC_TMASK) >> PROC_TSHIFT; } static void proc_set_safe_level(data) VALUE data; { ruby_safe_level = proc_get_safe_level(data); } static VALUE proc_alloc(klass, proc) VALUE klass; int proc; { volatile VALUE block; struct BLOCK *data, *p; struct RVarmap *vars; if (!rb_block_given_p() && !rb_f_block_given_p()) { rb_raise(rb_eArgError, "tried to create Proc object without a block"); } if (proc && !rb_block_given_p()) { rb_warn("tried to create Proc object without a block"); } if (!proc && ruby_block->block_obj && CLASS_OF(ruby_block->block_obj) == klass) { return ruby_block->block_obj; } block = Data_Make_Struct(klass, struct BLOCK, blk_mark, blk_free, data); *data = *ruby_block; data->orig_thread = rb_thread_current(); data->wrapper = ruby_wrapper; data->iter = data->prev?Qtrue:Qfalse; data->block_obj = block; frame_dup(&data->frame); if (data->iter) { blk_copy_prev(data); } else { data->prev = 0; } for (p = data; p; p = p->prev) { for (vars = p->dyna_vars; vars; vars = vars->next) { if (FL_TEST(vars, DVAR_DONT_RECYCLE)) break; FL_SET(vars, DVAR_DONT_RECYCLE); } } scope_dup(data->scope); proc_save_safe_level(block); if (proc) { data->flags |= BLOCK_LAMBDA; } else { ruby_block->block_obj = block; } return block; } /* * call-seq: * Proc.new {|...| block } => a_proc * Proc.new => a_proc * * Creates a new Proc object, bound to the current * context. Proc::new may be called without a block only * within a method with an attached block, in which case that block is * converted to the Proc object. * * def proc_from * Proc.new * end * proc = proc_from { "hello" } * proc.call #=> "hello" */ static VALUE proc_s_new(argc, argv, klass) int argc; VALUE *argv; VALUE klass; { VALUE block = proc_alloc(klass, Qfalse); rb_obj_call_init(block, argc, argv); return block; } VALUE rb_block_proc() { return proc_alloc(rb_cProc, Qfalse); } VALUE rb_f_lambda() { rb_warn("rb_f_lambda() is deprecated; use rb_block_proc() instead"); return proc_alloc(rb_cProc, Qtrue); } /* * call-seq: * proc { |...| block } => a_proc * lambda { |...| block } => a_proc * * Equivalent to Proc.new, except the resulting Proc objects * check the number of parameters passed when called. */ static VALUE proc_lambda() { return proc_alloc(rb_cProc, Qtrue); } static int block_orphan(data) struct BLOCK *data; { if (data->scope->flags & SCOPE_NOSTACK) { return 1; } if (data->orig_thread != rb_thread_current()) { return 1; } return 0; } static VALUE proc_invoke(proc, args, self, klass) VALUE proc, args; /* OK */ VALUE self, klass; { struct BLOCK * volatile old_block; struct BLOCK _block; struct BLOCK *data; volatile VALUE result = Qundef; int state; volatile int safe = ruby_safe_level; volatile VALUE old_wrapper = ruby_wrapper; volatile int pcall, avalue = Qtrue; volatile VALUE tmp = args; if (rb_block_given_p() && ruby_frame->last_func) { if (klass != ruby_frame->last_class) klass = rb_obj_class(proc); rb_warning("block for %s#%s is useless", rb_class2name(klass), rb_id2name(ruby_frame->last_func)); } Data_Get_Struct(proc, struct BLOCK, data); pcall = (data->flags & BLOCK_LAMBDA) ? YIELD_LAMBDA_CALL : 0; if (!pcall && RARRAY(args)->len == 1) { avalue = Qfalse; args = RARRAY(args)->ptr[0]; } PUSH_VARS(); ruby_wrapper = data->wrapper; ruby_dyna_vars = data->dyna_vars; /* PUSH BLOCK from data */ old_block = ruby_block; _block = *data; if (self != Qundef) _block.frame.self = self; if (klass) _block.frame.last_class = klass; _block.frame.argc = RARRAY(tmp)->len; _block.frame.flags = ruby_frame->flags; if (_block.frame.argc && DMETHOD_P()) { NEWOBJ(scope, struct SCOPE); OBJSETUP(scope, tmp, T_SCOPE); scope->local_tbl = _block.scope->local_tbl; scope->local_vars = _block.scope->local_vars; scope->flags |= SCOPE_CLONE | (_block.scope->flags & SCOPE_MALLOC); _block.scope = scope; } /* modify current frame */ ruby_block = &_block; PUSH_ITER(ITER_CUR); ruby_frame->iter = ITER_CUR; PUSH_TAG(pcall ? PROT_LAMBDA : PROT_NONE); state = EXEC_TAG(); if (state == 0) { proc_set_safe_level(proc); result = rb_yield_0(args, self, (self!=Qundef)?CLASS_OF(self):0, pcall, avalue); } else if (TAG_DST()) { result = prot_tag->retval; } POP_TAG(); POP_ITER(); ruby_block = old_block; ruby_wrapper = old_wrapper; POP_VARS(); ruby_safe_level = safe; switch (state) { case 0: break; case TAG_RETRY: proc_jump_error(TAG_RETRY, Qnil); /* xxx */ JUMP_TAG(state); break; case TAG_NEXT: case TAG_BREAK: if (!pcall && result != Qundef) { proc_jump_error(state, result); } case TAG_RETURN: if (result != Qundef) { if (pcall) break; return_jump(result); } default: JUMP_TAG(state); } return result; } /* CHECKME: are the argument checking semantics correct? */ /* * call-seq: * prc.call(params,...) => obj * prc[params,...] => obj * * Invokes the block, setting the block's parameters to the values in * params using something close to method calling semantics. * Generates a warning if multiple values are passed to a proc that * expects just one (previously this silently converted the parameters * to an array). * * For procs created using Kernel.proc, generates an * error if the wrong number of parameters * are passed to a proc with multiple parameters. For procs created using * Proc.new, extra parameters are silently discarded. * * Returns the value of the last expression evaluated in the block. See * also Proc#yield. * * a_proc = Proc.new {|a, *b| b.collect {|i| i*a }} * a_proc.call(9, 1, 2, 3) #=> [9, 18, 27] * a_proc[9, 1, 2, 3] #=> [9, 18, 27] * a_proc = Proc.new {|a,b| a} * a_proc.call(1,2,3) * * produces: * * prog.rb:5: wrong number of arguments (3 for 2) (ArgumentError) * from prog.rb:4:in `call' * from prog.rb:5 */ static VALUE proc_call(proc, args) VALUE proc, args; /* OK */ { return proc_invoke(proc, args, Qundef, 0); } static VALUE bmcall _((VALUE, VALUE)); static VALUE method_arity _((VALUE)); /* * call-seq: * prc.arity -> fixnum * * Returns the number of arguments that would not be ignored. If the block * is declared to take no arguments, returns 0. If the block is known * to take exactly n arguments, returns n. If the block has optional * arguments, return -n-1, where n is the number of mandatory * arguments. A proc with no argument declarations * is the same a block declaring || as its arguments. * * Proc.new {}.arity #=> 0 * Proc.new {||}.arity #=> 0 * Proc.new {|a|}.arity #=> 1 * Proc.new {|a,b|}.arity #=> 2 * Proc.new {|a,b,c|}.arity #=> 3 * Proc.new {|*a|}.arity #=> -1 * Proc.new {|a,*b|}.arity #=> -2 */ static VALUE proc_arity(proc) VALUE proc; { struct BLOCK *data; NODE *list; int n; Data_Get_Struct(proc, struct BLOCK, data); if (data->var == 0) { if (data->body && nd_type(data->body) == NODE_IFUNC && data->body->nd_cfnc == bmcall) { return method_arity(data->body->nd_tval); } return INT2FIX(-1); } if (data->var == (NODE*)1) return INT2FIX(0); if (data->var == (NODE*)2) return INT2FIX(0); switch (nd_type(data->var)) { default: return INT2FIX(1); case NODE_MASGN: list = data->var->nd_head; n = 0; while (list) { n++; list = list->nd_next; } if (data->var->nd_args) return INT2FIX(-n-1); return INT2FIX(n); } } /* * call-seq: * prc == other_proc => true or false * * Return true if prc is the same object as * other_proc, or if they are both procs with the same body. */ static VALUE proc_eq(self, other) VALUE self, other; { struct BLOCK *data, *data2; if (self == other) return Qtrue; if (TYPE(other) != T_DATA) return Qfalse; if (RDATA(other)->dmark != (RUBY_DATA_FUNC)blk_mark) return Qfalse; if (CLASS_OF(self) != CLASS_OF(other)) return Qfalse; Data_Get_Struct(self, struct BLOCK, data); Data_Get_Struct(other, struct BLOCK, data2); if (data->body != data2->body) return Qfalse; if (data->var != data2->var) return Qfalse; if (data->scope != data2->scope) return Qfalse; if (data->dyna_vars != data2->dyna_vars) return Qfalse; if (data->flags != data2->flags) return Qfalse; return Qtrue; } /* * call-seq: * prc.to_s => string * * Shows the unique identifier for this proc, along with * an indication of where the proc was defined. */ static VALUE proc_to_s(self) VALUE self; { struct BLOCK *data; NODE *node; char *cname = rb_obj_classname(self); const int w = (sizeof(VALUE) * CHAR_BIT) / 4; long len = strlen(cname)+6+w; /* 6:tags 16:addr */ VALUE str; Data_Get_Struct(self, struct BLOCK, data); if ((node = data->frame.node) || (node = data->body)) { len += strlen(node->nd_file) + 2 + (SIZEOF_LONG*CHAR_BIT-NODE_LSHIFT)/3; str = rb_str_new(0, len); snprintf(RSTRING(str)->ptr, len+1, "#<%s:0x%.*lx@%s:%d>", cname, w, (VALUE)data->body, node->nd_file, nd_line(node)); } else { str = rb_str_new(0, len); snprintf(RSTRING(str)->ptr, len+1, "#<%s:0x%.*lx>", cname, w, (VALUE)data->body); } RSTRING(str)->len = strlen(RSTRING(str)->ptr); if (OBJ_TAINTED(self)) OBJ_TAINT(str); return str; } /* * call-seq: * prc.to_proc -> prc * * Part of the protocol for converting objects to Proc * objects. Instances of class Proc simply return * themselves. */ static VALUE proc_to_self(self) VALUE self; { return self; } /* * call-seq: * prc.binding => binding * * Returns the binding associated with prc. Note that * Kernel#eval accepts either a Proc or a * Binding object as its second parameter. * * def fred(param) * proc {} * end * * b = fred(99) * eval("param", b.binding) #=> 99 * eval("param", b) #=> 99 */ static VALUE proc_binding(proc) VALUE proc; { struct BLOCK *orig, *data; VALUE bind; Data_Get_Struct(proc, struct BLOCK, orig); bind = Data_Make_Struct(rb_cBinding,struct BLOCK,blk_mark,blk_free,data); MEMCPY(data, orig, struct BLOCK, 1); frame_dup(&data->frame); if (data->iter) { blk_copy_prev(data); } else { data->prev = 0; } return bind; } static VALUE block_pass(self, node) VALUE self; NODE *node; { VALUE proc = rb_eval(self, node->nd_body); /* OK */ VALUE b; struct BLOCK * volatile old_block; struct BLOCK _block; struct BLOCK *data; volatile VALUE result = Qnil; int state; volatile int orphan; volatile int safe = ruby_safe_level; if (NIL_P(proc)) { PUSH_ITER(ITER_NOT); result = rb_eval(self, node->nd_iter); POP_ITER(); return result; } if (!rb_obj_is_proc(proc)) { b = rb_check_convert_type(proc, T_DATA, "Proc", "to_proc"); if (!rb_obj_is_proc(b)) { rb_raise(rb_eTypeError, "wrong argument type %s (expected Proc)", rb_obj_classname(proc)); } proc = b; } if (ruby_safe_level >= 1 && OBJ_TAINTED(proc) && ruby_safe_level > proc_get_safe_level(proc)) { rb_raise(rb_eSecurityError, "Insecure: tainted block value"); } if (ruby_block && ruby_block->block_obj == proc) { PUSH_ITER(ITER_PAS); result = rb_eval(self, node->nd_iter); POP_ITER(); return result; } Data_Get_Struct(proc, struct BLOCK, data); orphan = block_orphan(data); /* PUSH BLOCK from data */ old_block = ruby_block; _block = *data; _block.outer = ruby_block; if (orphan) _block.uniq = block_unique++; ruby_block = &_block; PUSH_ITER(ITER_PRE); if (ruby_frame->iter == ITER_NOT) ruby_frame->iter = ITER_PRE; PUSH_TAG(PROT_LOOP); state = EXEC_TAG(); if (state == 0) { retry: proc_set_safe_level(proc); if (safe > ruby_safe_level) ruby_safe_level = safe; result = rb_eval(self, node->nd_iter); } else if (state == TAG_BREAK && TAG_DST()) { result = prot_tag->retval; state = 0; } else if (state == TAG_RETRY) { state = 0; goto retry; } POP_TAG(); POP_ITER(); ruby_block = old_block; ruby_safe_level = safe; switch (state) {/* escape from orphan block */ case 0: break; case TAG_RETURN: if (orphan) { proc_jump_error(state, prot_tag->retval); } default: JUMP_TAG(state); } return result; } struct METHOD { VALUE klass, rklass; VALUE recv; ID id, oid; int safe_level; NODE *body; }; static void bm_mark(data) struct METHOD *data; { rb_gc_mark(data->rklass); rb_gc_mark(data->klass); rb_gc_mark(data->recv); rb_gc_mark((VALUE)data->body); } static VALUE mnew(klass, obj, id, mklass) VALUE klass, obj, mklass; ID id; { VALUE method; NODE *body; int noex; struct METHOD *data; VALUE rklass = klass; ID oid = id; again: if ((body = rb_get_method_body(&klass, &id, &noex)) == 0) { print_undef(rklass, oid); } if (nd_type(body) == NODE_ZSUPER) { klass = RCLASS(klass)->super; goto again; } while (rklass != klass && (FL_TEST(rklass, FL_SINGLETON) || TYPE(rklass) == T_ICLASS)) { rklass = RCLASS(rklass)->super; } method = Data_Make_Struct(mklass, struct METHOD, bm_mark, free, data); data->klass = klass; data->recv = obj; data->id = id; data->body = body; data->rklass = rklass; data->oid = oid; data->safe_level = NOEX_WITH_SAFE(noex); OBJ_INFECT(method, klass); return method; } /********************************************************************** * * Document-class : Method * * Method objects are created by Object#method, and are * associated with a particular object (not just with a class). They * may be used to invoke the method within the object, and as a block * associated with an iterator. They may also be unbound from one * object (creating an UnboundMethod) and bound to * another. * * class Thing * def square(n) * n*n * end * end * thing = Thing.new * meth = thing.method(:square) * * meth.call(9) #=> 81 * [ 1, 2, 3 ].collect(&meth) #=> [1, 4, 9] * */ /* * call-seq: * meth == other_meth => true or false * * Two method objects are equal if that are bound to the same * object and contain the same body. */ static VALUE method_eq(method, other) VALUE method, other; { struct METHOD *m1, *m2; if (TYPE(other) != T_DATA || RDATA(other)->dmark != (RUBY_DATA_FUNC)bm_mark) return Qfalse; if (CLASS_OF(method) != CLASS_OF(other)) return Qfalse; Data_Get_Struct(method, struct METHOD, m1); Data_Get_Struct(other, struct METHOD, m2); if (m1->klass != m2->klass || m1->rklass != m2->rklass || m1->recv != m2->recv || m1->body != m2->body) return Qfalse; return Qtrue; } /* * call-seq: * meth.unbind => unbound_method * * Dissociates meth from it's current receiver. The resulting * UnboundMethod can subsequently be bound to a new object * of the same class (see UnboundMethod). */ static VALUE method_unbind(obj) VALUE obj; { VALUE method; struct METHOD *orig, *data; Data_Get_Struct(obj, struct METHOD, orig); method = Data_Make_Struct(rb_cUnboundMethod, struct METHOD, bm_mark, free, data); data->klass = orig->klass; data->recv = Qundef; data->id = orig->id; data->body = orig->body; data->rklass = orig->rklass; data->oid = orig->oid; OBJ_INFECT(method, obj); return method; } /* * call-seq: * obj.method(sym) => method * * Looks up the named method as a receiver in obj, returning a * Method object (or raising NameError). The * Method object acts as a closure in obj's object * instance, so instance variables and the value of self * remain available. * * class Demo * def initialize(n) * @iv = n * end * def hello() * "Hello, @iv = #{@iv}" * end * end * * k = Demo.new(99) * m = k.method(:hello) * m.call #=> "Hello, @iv = 99" * * l = Demo.new('Fred') * m = l.method("hello") * m.call #=> "Hello, @iv = Fred" */ static VALUE rb_obj_method(obj, vid) VALUE obj; VALUE vid; { return mnew(CLASS_OF(obj), obj, rb_to_id(vid), rb_cMethod); } /* * call-seq: * mod.instance_method(symbol) => unbound_method * * Returns an +UnboundMethod+ representing the given * instance method in _mod_. * * class Interpreter * def do_a() print "there, "; end * def do_d() print "Hello "; end * def do_e() print "!\n"; end * def do_v() print "Dave"; end * Dispatcher = { * ?a => instance_method(:do_a), * ?d => instance_method(:do_d), * ?e => instance_method(:do_e), * ?v => instance_method(:do_v) * } * def interpret(string) * string.each_byte {|b| Dispatcher[b].bind(self).call } * end * end * * * interpreter = Interpreter.new * interpreter.interpret('dave') * * produces: * * Hello there, Dave! */ static VALUE rb_mod_method(mod, vid) VALUE mod; VALUE vid; { return mnew(mod, Qundef, rb_to_id(vid), rb_cUnboundMethod); } /* * MISSING: documentation */ static VALUE method_clone(self) VALUE self; { VALUE clone; struct METHOD *orig, *data; Data_Get_Struct(self, struct METHOD, orig); clone = Data_Make_Struct(CLASS_OF(self),struct METHOD, bm_mark, free, data); CLONESETUP(clone, self); *data = *orig; return clone; } /* * call-seq: * meth.call(args, ...) => obj * meth[args, ...] => obj * * Invokes the meth with the specified arguments, returning the * method's return value. * * m = 12.method("+") * m.call(3) #=> 15 * m.call(20) #=> 32 */ static VALUE method_call(argc, argv, method) int argc; VALUE *argv; VALUE method; { VALUE result = Qnil; /* OK */ struct METHOD *data; int safe; Data_Get_Struct(method, struct METHOD, data); if (data->recv == Qundef) { rb_raise(rb_eTypeError, "can't call unbound method; bind first"); } if (OBJ_TAINTED(method)) { safe = NOEX_WITH(data->safe_level, 4)|NOEX_TAINTED; } else { safe = data->safe_level; } PUSH_ITER(rb_block_given_p()?ITER_PRE:ITER_NOT); result = rb_call0(data->klass,data->recv,data->id,data->oid,argc,argv,data->body,safe); POP_ITER(); return result; } /********************************************************************** * * Document-class: UnboundMethod * * Ruby supports two forms of objectified methods. Class * Method is used to represent methods that are associated * with a particular object: these method objects are bound to that * object. Bound method objects for an object can be created using * Object#method. * * Ruby also supports unbound methods; methods objects that are not * associated with a particular object. These can be created either by * calling Module#instance_method or by calling * unbind on a bound method object. The result of both of * these is an UnboundMethod object. * * Unbound methods can only be called after they are bound to an * object. That object must be be a kind_of? the method's original * class. * * class Square * def area * @side * @side * end * def initialize(side) * @side = side * end * end * * area_un = Square.instance_method(:area) * * s = Square.new(12) * area = area_un.bind(s) * area.call #=> 144 * * Unbound methods are a reference to the method at the time it was * objectified: subsequent changes to the underlying class will not * affect the unbound method. * * class Test * def test * :original * end * end * um = Test.instance_method(:test) * class Test * def test * :modified * end * end * t = Test.new * t.test #=> :modified * um.bind(t).call #=> :original * */ /* * call-seq: * umeth.bind(obj) -> method * * Bind umeth to obj. If Klass was the class * from which umeth was obtained, * obj.kind_of?(Klass) must be true. * * class A * def test * puts "In test, class = #{self.class}" * end * end * class B < A * end * class C < B * end * * * um = B.instance_method(:test) * bm = um.bind(C.new) * bm.call * bm = um.bind(B.new) * bm.call * bm = um.bind(A.new) * bm.call * * produces: * * In test, class = C * In test, class = B * prog.rb:16:in `bind': bind argument must be an instance of B (TypeError) * from prog.rb:16 */ static VALUE umethod_bind(method, recv) VALUE method, recv; { struct METHOD *data, *bound; VALUE rklass = CLASS_OF(recv); Data_Get_Struct(method, struct METHOD, data); if (data->rklass != rklass) { if (FL_TEST(data->rklass, FL_SINGLETON)) { rb_raise(rb_eTypeError, "singleton method bound for a different object"); } if (TYPE(data->rklass) == T_MODULE) { st_table *m_tbl = RCLASS(data->rklass)->m_tbl; while (RCLASS(rklass)->m_tbl != m_tbl) { rklass = RCLASS(rklass)->super; if (!rklass) goto not_instace; } } else if (!rb_obj_is_kind_of(recv, data->rklass)) { not_instace: rb_raise(rb_eTypeError, "bind argument must be an instance of %s", rb_class2name(data->rklass)); } } method = Data_Make_Struct(rb_cMethod,struct METHOD,bm_mark,free,bound); *bound = *data; bound->recv = recv; bound->rklass = rklass; return method; } /* * call-seq: * meth.arity => fixnum * * Returns an indication of the number of arguments accepted by a * method. Returns a nonnegative integer for methods that take a fixed * number of arguments. For Ruby methods that take a variable number of * arguments, returns -n-1, where n is the number of required * arguments. For methods written in C, returns -1 if the call takes a * variable number of arguments. * * class C * def one; end * def two(a); end * def three(*a); end * def four(a, b); end * def five(a, b, *c); end * def six(a, b, *c, &d); end * end * c = C.new * c.method(:one).arity #=> 0 * c.method(:two).arity #=> 1 * c.method(:three).arity #=> -1 * c.method(:four).arity #=> 2 * c.method(:five).arity #=> -3 * c.method(:six).arity #=> -3 * * "cat".method(:size).arity #=> 0 * "cat".method(:replace).arity #=> 1 * "cat".method(:squeeze).arity #=> -1 * "cat".method(:count).arity #=> -1 */ static VALUE method_arity(method) VALUE method; { struct METHOD *data; NODE *body; int n; Data_Get_Struct(method, struct METHOD, data); body = data->body; switch (nd_type(body)) { case NODE_CFUNC: if (body->nd_argc < 0) return INT2FIX(-1); return INT2FIX(body->nd_argc); case NODE_ZSUPER: return INT2FIX(-1); case NODE_ATTRSET: return INT2FIX(1); case NODE_IVAR: return INT2FIX(0); case NODE_BMETHOD: return proc_arity(body->nd_cval); case NODE_DMETHOD: return method_arity(body->nd_cval); case NODE_SCOPE: body = body->nd_next; /* skip NODE_SCOPE */ if (nd_type(body) == NODE_BLOCK) body = body->nd_head; if (!body) return INT2FIX(0); n = body->nd_cnt; if (body->nd_opt || body->nd_rest) n = -n-1; return INT2FIX(n); default: rb_raise(rb_eArgError, "invalid node 0x%x", nd_type(body)); } } /* * call-seq: * meth.to_s => string * meth.inspect => string * * Show the name of the underlying method. * * "cat".method(:count).inspect #=> "#" */ static VALUE method_inspect(method) VALUE method; { struct METHOD *data; VALUE str; const char *s; char *sharp = "#"; Data_Get_Struct(method, struct METHOD, data); str = rb_str_buf_new2("#<"); s = rb_obj_classname(method); rb_str_buf_cat2(str, s); rb_str_buf_cat2(str, ": "); if (FL_TEST(data->klass, FL_SINGLETON)) { VALUE v = rb_iv_get(data->klass, "__attached__"); if (data->recv == Qundef) { rb_str_buf_append(str, rb_inspect(data->klass)); } else if (data->recv == v) { rb_str_buf_append(str, rb_inspect(v)); sharp = "."; } else { rb_str_buf_append(str, rb_inspect(data->recv)); rb_str_buf_cat2(str, "("); rb_str_buf_append(str, rb_inspect(v)); rb_str_buf_cat2(str, ")"); sharp = "."; } } else { rb_str_buf_cat2(str, rb_class2name(data->rklass)); if (data->rklass != data->klass) { VALUE klass = data -> klass; if (TYPE(klass) == T_ICLASS) { klass = RBASIC(klass)->klass; } rb_str_buf_cat2(str, "("); rb_str_buf_cat2(str, rb_class2name(klass)); rb_str_buf_cat2(str, ")"); } } rb_str_buf_cat2(str, sharp); rb_str_buf_cat2(str, rb_id2name(data->oid)); rb_str_buf_cat2(str, ">"); return str; } static VALUE mproc(method) VALUE method; { VALUE proc; /* emulate ruby's method call */ PUSH_ITER(ITER_CUR); PUSH_FRAME(); proc = rb_block_proc(); POP_FRAME(); POP_ITER(); return proc; } static VALUE bmcall(args, method) VALUE args, method; { volatile VALUE a; VALUE ret; a = svalue_to_avalue(args); ret = method_call(RARRAY(a)->len, RARRAY(a)->ptr, method); a = Qnil; /* prevent tail call */ return ret; } VALUE rb_proc_new(func, val) VALUE (*func)(ANYARGS); /* VALUE yieldarg[, VALUE procarg] */ VALUE val; { struct BLOCK *data; VALUE proc = rb_iterate((VALUE(*)_((VALUE)))mproc, 0, func, val); Data_Get_Struct(proc, struct BLOCK, data); data->body->nd_state = YIELD_FUNC_AVALUE; return proc; } /* * call-seq: * meth.to_proc => prc * * Returns a Proc object corresponding to this method. */ static VALUE method_proc(method) VALUE method; { VALUE proc; struct METHOD *mdata; struct BLOCK *bdata; proc = rb_iterate((VALUE(*)_((VALUE)))mproc, 0, bmcall, method); Data_Get_Struct(method, struct METHOD, mdata); Data_Get_Struct(proc, struct BLOCK, bdata); bdata->body->nd_file = mdata->body->nd_file; nd_set_line(bdata->body, nd_line(mdata->body)); bdata->body->nd_state = YIELD_FUNC_SVALUE; return proc; } static VALUE rb_obj_is_method(m) VALUE m; { if (TYPE(m) == T_DATA && RDATA(m)->dmark == (RUBY_DATA_FUNC)bm_mark) { return Qtrue; } return Qfalse; } /* * call-seq: * define_method(symbol, method) => new_method * define_method(symbol) { block } => proc * * Defines an instance method in the receiver. The _method_ * parameter can be a +Proc+ or +Method+ object. * If a block is specified, it is used as the method body. This block * is evaluated using instance_eval, a point that is * tricky to demonstrate because define_method is private. * (This is why we resort to the +send+ hack in this example.) * * class A * def fred * puts "In Fred" * end * def create_method(name, &block) * self.class.send(:define_method, name, &block) * end * define_method(:wilma) { puts "Charge it!" } * end * class B < A * define_method(:barney, instance_method(:fred)) * end * a = B.new * a.barney * a.wilma * a.create_method(:betty) { p self } * a.betty * * produces: * * In Fred * Charge it! * # */ static VALUE rb_mod_define_method(argc, argv, mod) int argc; VALUE *argv; VALUE mod; { ID id; VALUE body; NODE *node; int noex; if (argc == 1) { id = rb_to_id(argv[0]); body = proc_lambda(); } else if (argc == 2) { id = rb_to_id(argv[0]); body = argv[1]; if (!rb_obj_is_method(body) && !rb_obj_is_proc(body)) { rb_raise(rb_eTypeError, "wrong argument type %s (expected Proc/Method)", rb_obj_classname(body)); } } else { rb_raise(rb_eArgError, "wrong number of arguments (%d for 1)", argc); } if (RDATA(body)->dmark == (RUBY_DATA_FUNC)bm_mark) { node = NEW_DMETHOD(method_unbind(body)); } else if (RDATA(body)->dmark == (RUBY_DATA_FUNC)blk_mark) { struct BLOCK *block; body = proc_clone(body); Data_Get_Struct(body, struct BLOCK, block); block->frame.last_func = id; block->frame.orig_func = id; block->frame.last_class = mod; node = NEW_BMETHOD(body); } else { /* type error */ rb_raise(rb_eTypeError, "wrong argument type (expected Proc/Method)"); } noex = NOEX_PUBLIC; if (ruby_cbase == mod) { if (SCOPE_TEST(SCOPE_PRIVATE)) { noex = NOEX_PRIVATE; } else if (SCOPE_TEST(SCOPE_PROTECTED)) { noex = NOEX_PROTECTED; } } rb_add_method(mod, id, node, noex); return body; } /* * Proc objects are blocks of code that have been bound to * a set of local variables. Once bound, the code may be called in * different contexts and still access those variables. * * def gen_times(factor) * return Proc.new {|n| n*factor } * end * * times3 = gen_times(3) * times5 = gen_times(5) * * times3.call(12) #=> 36 * times5.call(5) #=> 25 * times3.call(times5.call(4)) #=> 60 * */ void Init_Proc() { rb_eLocalJumpError = rb_define_class("LocalJumpError", rb_eStandardError); rb_define_method(rb_eLocalJumpError, "exit_value", localjump_xvalue, 0); rb_define_method(rb_eLocalJumpError, "reason", localjump_reason, 0); rb_global_variable(&exception_error); exception_error = rb_exc_new3(rb_eFatal, rb_obj_freeze(rb_str_new2("exception reentered"))); OBJ_TAINT(exception_error); OBJ_FREEZE(exception_error); rb_eSysStackError = rb_define_class("SystemStackError", rb_eStandardError); rb_global_variable(&sysstack_error); sysstack_error = rb_exc_new3(rb_eSysStackError, rb_obj_freeze(rb_str_new2("stack level too deep"))); OBJ_TAINT(sysstack_error); OBJ_FREEZE(sysstack_error); rb_cProc = rb_define_class("Proc", rb_cObject); rb_undef_alloc_func(rb_cProc); rb_define_singleton_method(rb_cProc, "new", proc_s_new, -1); rb_define_method(rb_cProc, "clone", proc_clone, 0); rb_define_method(rb_cProc, "dup", proc_dup, 0); rb_define_method(rb_cProc, "call", proc_call, -2); rb_define_method(rb_cProc, "arity", proc_arity, 0); rb_define_method(rb_cProc, "[]", proc_call, -2); rb_define_method(rb_cProc, "==", proc_eq, 1); rb_define_method(rb_cProc, "to_s", proc_to_s, 0); rb_define_method(rb_cProc, "to_proc", proc_to_self, 0); rb_define_method(rb_cProc, "binding", proc_binding, 0); rb_define_global_function("proc", proc_lambda, 0); rb_define_global_function("lambda", proc_lambda, 0); rb_cMethod = rb_define_class("Method", rb_cObject); rb_undef_alloc_func(rb_cMethod); rb_undef_method(CLASS_OF(rb_cMethod), "new"); rb_define_method(rb_cMethod, "==", method_eq, 1); rb_define_method(rb_cMethod, "clone", method_clone, 0); rb_define_method(rb_cMethod, "call", method_call, -1); rb_define_method(rb_cMethod, "[]", method_call, -1); rb_define_method(rb_cMethod, "arity", method_arity, 0); rb_define_method(rb_cMethod, "inspect", method_inspect, 0); rb_define_method(rb_cMethod, "to_s", method_inspect, 0); rb_define_method(rb_cMethod, "to_proc", method_proc, 0); rb_define_method(rb_cMethod, "unbind", method_unbind, 0); rb_define_method(rb_mKernel, "method", rb_obj_method, 1); rb_cUnboundMethod = rb_define_class("UnboundMethod", rb_cObject); rb_undef_alloc_func(rb_cUnboundMethod); rb_undef_method(CLASS_OF(rb_cUnboundMethod), "new"); rb_define_method(rb_cUnboundMethod, "==", method_eq, 1); rb_define_method(rb_cUnboundMethod, "clone", method_clone, 0); rb_define_method(rb_cUnboundMethod, "arity", method_arity, 0); rb_define_method(rb_cUnboundMethod, "inspect", method_inspect, 0); rb_define_method(rb_cUnboundMethod, "to_s", method_inspect, 0); rb_define_method(rb_cUnboundMethod, "bind", umethod_bind, 1); rb_define_method(rb_cModule, "instance_method", rb_mod_method, 1); } /* * Objects of class Binding encapsulate the execution * context at some particular place in the code and retain this context * for future use. The variables, methods, value of self, * and possibly an iterator block that can be accessed in this context * are all retained. Binding objects can be created using * Kernel#binding, and are made available to the callback * of Kernel#set_trace_func. * * These binding objects can be passed as the second argument of the * Kernel#eval method, establishing an environment for the * evaluation. * * class Demo * def initialize(n) * @secret = n * end * def getBinding * return binding() * end * end * * k1 = Demo.new(99) * b1 = k1.getBinding * k2 = Demo.new(-3) * b2 = k2.getBinding * * eval("@secret", b1) #=> 99 * eval("@secret", b2) #=> -3 * eval("@secret") #=> nil * * Binding objects have no class-specific methods. * */ void Init_Binding() { rb_cBinding = rb_define_class("Binding", rb_cObject); rb_undef_alloc_func(rb_cBinding); rb_undef_method(CLASS_OF(rb_cBinding), "new"); rb_define_method(rb_cBinding, "clone", proc_clone, 0); rb_define_method(rb_cBinding, "dup", proc_dup, 0); rb_define_global_function("binding", rb_f_binding, 0); } /* Windows SEH refers data on the stack. */ #undef SAVE_WIN32_EXCEPTION_LIST #if defined _WIN32 || defined __CYGWIN__ #if defined __CYGWIN__ typedef unsigned long DWORD; #endif static inline DWORD win32_get_exception_list() { DWORD p; # if defined _MSC_VER # ifdef _M_IX86 # define SAVE_WIN32_EXCEPTION_LIST # if _MSC_VER >= 1310 /* warning: unsafe assignment to fs:0 ... this is ok */ # pragma warning(disable: 4733) # endif __asm mov eax, fs:[0]; __asm mov p, eax; # endif # elif defined __GNUC__ # ifdef __i386__ # define SAVE_WIN32_EXCEPTION_LIST __asm__("movl %%fs:0,%0" : "=r"(p)); # endif # elif defined __BORLANDC__ # define SAVE_WIN32_EXCEPTION_LIST __emit__(0x64, 0xA1, 0, 0, 0, 0); /* mov eax, fs:[0] */ p = _EAX; # endif return p; } static inline void win32_set_exception_list(p) DWORD p; { # if defined _MSC_VER # ifdef _M_IX86 __asm mov eax, p; __asm mov fs:[0], eax; # endif # elif defined __GNUC__ # ifdef __i386__ __asm__("movl %0,%%fs:0" :: "r"(p)); # endif # elif defined __BORLANDC__ _EAX = p; __emit__(0x64, 0xA3, 0, 0, 0, 0); /* mov fs:[0], eax */ # endif } #if !defined SAVE_WIN32_EXCEPTION_LIST && !defined _WIN32_WCE # error unsupported platform #endif #endif int rb_thread_pending = 0; VALUE rb_cThread; extern VALUE rb_last_status; #define WAIT_FD (1<<0) #define WAIT_SELECT (1<<1) #define WAIT_TIME (1<<2) #define WAIT_JOIN (1<<3) #define WAIT_PID (1<<4) /* +infty, for this purpose */ #define DELAY_INFTY 1E30 #if !defined HAVE_PAUSE # if defined _WIN32 && !defined __CYGWIN__ # define pause() Sleep(INFINITE) # else # define pause() sleep(0x7fffffff) # endif #endif #define THREAD_TERMINATING 0x400 /* persistent flag */ #define THREAD_NO_ENSURE 0x800 /* persistent flag */ #define THREAD_FLAGS_MASK 0xfc00 /* mask for persistent flags */ #define FOREACH_THREAD_FROM(f,x) x = f; do { x = x->next; #define END_FOREACH_FROM(f,x) } while (x != f) #define FOREACH_THREAD(x) FOREACH_THREAD_FROM(curr_thread,x) #define END_FOREACH(x) END_FOREACH_FROM(curr_thread,x) struct thread_status_t { NODE *node; int tracing; VALUE errinfo; VALUE last_status; VALUE last_line; VALUE last_match; int safe; enum rb_thread_status status; int wait_for; int fd; fd_set readfds; fd_set writefds; fd_set exceptfds; int select_value; double delay; rb_thread_t join; }; #define THREAD_COPY_STATUS(src, dst) (void)( \ (dst)->node = (src)->node, \ \ (dst)->tracing = (src)->tracing, \ (dst)->errinfo = (src)->errinfo, \ (dst)->last_status = (src)->last_status, \ (dst)->last_line = (src)->last_line, \ (dst)->last_match = (src)->last_match, \ \ (dst)->safe = (src)->safe, \ \ (dst)->status = (src)->status, \ (dst)->wait_for = (src)->wait_for, \ (dst)->fd = (src)->fd, \ (dst)->readfds = (src)->readfds, \ (dst)->writefds = (src)->writefds, \ (dst)->exceptfds = (src)->exceptfds, \ (dst)->select_value = (src)->select_value, \ (dst)->delay = (src)->delay, \ (dst)->join = (src)->join, \ 0) int rb_thread_set_raised(th) rb_thread_t th; { if (th->flags & RAISED_EXCEPTION) { return 1; } th->flags |= RAISED_EXCEPTION; return 0; } int rb_thread_reset_raised(th) rb_thread_t th; { if (!(th->flags & RAISED_EXCEPTION)) { return 0; } th->flags &= ~RAISED_EXCEPTION; return 1; } static int thread_no_ensure() { return ((curr_thread->flags & THREAD_NO_ENSURE) == THREAD_NO_ENSURE); } static void rb_thread_ready _((rb_thread_t)); static VALUE run_trap_eval _((VALUE)); static VALUE run_trap_eval(arg) VALUE arg; { VALUE *p = (VALUE *)arg; return rb_eval_cmd(p[0], p[1], (int)p[2]); } static VALUE rb_trap_eval(cmd, sig, safe) VALUE cmd; int sig, safe; { int state; VALUE val = Qnil; /* OK */ volatile struct thread_status_t save; VALUE arg[3]; arg[0] = cmd; arg[1] = rb_ary_new3(1, INT2FIX(sig)); arg[2] = (VALUE)safe; THREAD_COPY_STATUS(curr_thread, &save); rb_thread_ready(curr_thread); PUSH_ITER(ITER_NOT); val = rb_protect(run_trap_eval, (VALUE)&arg, &state); POP_ITER(); THREAD_COPY_STATUS(&save, curr_thread); if (state) { rb_trap_immediate = 0; rb_thread_ready(curr_thread); JUMP_TAG(state); } if (curr_thread->status == THREAD_STOPPED) { rb_thread_schedule(); } errno = EINTR; return val; } static const char * thread_status_name(status) enum rb_thread_status status; { switch (status) { case THREAD_RUNNABLE: return "run"; case THREAD_STOPPED: return "sleep"; case THREAD_TO_KILL: return "aborting"; case THREAD_KILLED: return "dead"; default: return "unknown"; } } /* $SAFE accessor */ void rb_set_safe_level(level) int level; { if (level > ruby_safe_level) { if (level > SAFE_LEVEL_MAX) level = SAFE_LEVEL_MAX; ruby_safe_level = level; curr_thread->safe = level; } } static VALUE safe_getter() { return INT2NUM(ruby_safe_level); } static void safe_setter(val) VALUE val; { int level = NUM2INT(val); if (level < ruby_safe_level) { rb_raise(rb_eSecurityError, "tried to downgrade safe level from %d to %d", ruby_safe_level, level); } if (level > SAFE_LEVEL_MAX) level = SAFE_LEVEL_MAX; ruby_safe_level = level; curr_thread->safe = level; } /* Return the current time as a floating-point number */ static double timeofday() { struct timeval tv; #ifdef CLOCK_MONOTONIC struct timespec tp; if (clock_gettime(CLOCK_MONOTONIC, &tp) == 0) { return (double)tp.tv_sec + (double)tp.tv_nsec * 1e-9; } #endif gettimeofday(&tv, NULL); return (double)tv.tv_sec + (double)tv.tv_usec * 1e-6; } #define STACK(addr) (th->stk_pos<(VALUE*)(addr) && (VALUE*)(addr)stk_pos+th->stk_len) #define ADJ(addr) (void*)(STACK(addr)?(((VALUE*)(addr)-th->stk_pos)+th->stk_ptr):(VALUE*)(addr)) static void thread_mark(th) rb_thread_t th; { struct FRAME *frame; struct BLOCK *block; rb_gc_mark(th->result); rb_gc_mark(th->thread); if (th->join) rb_gc_mark(th->join->thread); rb_gc_mark(th->klass); rb_gc_mark(th->wrapper); rb_gc_mark((VALUE)th->cref); rb_gc_mark((VALUE)th->scope); rb_gc_mark((VALUE)th->dyna_vars); rb_gc_mark(th->errinfo); rb_gc_mark(th->last_status); rb_gc_mark(th->last_line); rb_gc_mark(th->last_match); rb_mark_tbl(th->locals); rb_gc_mark(th->thgroup); rb_gc_mark_maybe(th->sandbox); /* mark data in copied stack */ if (th == curr_thread) return; if (th->status == THREAD_KILLED) return; if (th->stk_len == 0) return; /* stack not active, no need to mark. */ if (th->stk_ptr) { rb_gc_mark_locations(th->stk_ptr, th->stk_ptr+th->stk_len); #if defined(THINK_C) || defined(__human68k__) rb_gc_mark_locations(th->stk_ptr+2, th->stk_ptr+th->stk_len+2); #endif #ifdef __ia64 if (th->bstr_ptr) { rb_gc_mark_locations(th->bstr_ptr, th->bstr_ptr+th->bstr_len); } #endif } frame = th->frame; while (frame && frame != top_frame) { frame = ADJ(frame); rb_gc_mark_frame(frame); if (frame->tmp) { struct FRAME *tmp = frame->tmp; while (tmp && tmp != top_frame) { tmp = ADJ(tmp); rb_gc_mark_frame(tmp); tmp = tmp->prev; } } frame = frame->prev; } block = th->block; while (block) { block = ADJ(block); rb_gc_mark_frame(&block->frame); block = block->prev; } } static int mark_loading_thread(key, value, lev) ID key; VALUE value; int lev; { rb_gc_mark(((rb_thread_t)value)->thread); return ST_CONTINUE; } void rb_gc_mark_threads() { rb_thread_t th; /* static global mark */ rb_gc_mark((VALUE)ruby_cref); if (!curr_thread) return; rb_gc_mark(main_thread->thread); rb_gc_mark(curr_thread->thread); FOREACH_THREAD_FROM(main_thread, th) { switch (th->status) { case THREAD_TO_KILL: case THREAD_RUNNABLE: break; case THREAD_STOPPED: if (th->wait_for) break; default: continue; } rb_gc_mark(th->thread); } END_FOREACH_FROM(main_thread, th); if (loading_tbl) st_foreach(loading_tbl, mark_loading_thread, 0); } void rb_gc_abort_threads() { rb_thread_t th; if (!main_thread) return; FOREACH_THREAD_FROM(main_thread, th) { if (FL_TEST(th->thread, FL_MARK)) continue; if (th->status == THREAD_STOPPED) { th->status = THREAD_TO_KILL; rb_gc_mark(th->thread); } } END_FOREACH_FROM(main_thread, th); } static inline void stack_free(th) rb_thread_t th; { if (th->stk_ptr) free(th->stk_ptr); th->stk_ptr = 0; #ifdef __ia64 if (th->bstr_ptr) free(th->bstr_ptr); th->bstr_ptr = 0; #endif } static void thread_free(th) rb_thread_t th; { stack_free(th); if (th->locals) st_free_table(th->locals); if (th->status != THREAD_KILLED) { if (th->prev) th->prev->next = th->next; if (th->next) th->next->prev = th->prev; } if (th != main_thread) free(th); } static rb_thread_t rb_thread_check(data) VALUE data; { if (TYPE(data) != T_DATA || RDATA(data)->dmark != (RUBY_DATA_FUNC)thread_mark) { rb_raise(rb_eTypeError, "wrong argument type %s (expected Thread)", rb_obj_classname(data)); } return (rb_thread_t)RDATA(data)->data; } static VALUE rb_thread_raise _((int, VALUE*, rb_thread_t)); static VALUE th_raise_exception; static NODE *th_raise_node; static VALUE th_cmd; static int th_sig, th_safe; #define RESTORE_NORMAL 1 #define RESTORE_FATAL 2 #define RESTORE_INTERRUPT 3 #define RESTORE_TRAP 4 #define RESTORE_RAISE 5 #define RESTORE_SIGNAL 6 #define RESTORE_EXIT 7 extern VALUE *rb_gc_stack_start; #ifdef __ia64 extern VALUE *rb_gc_register_stack_start; #endif static void rb_thread_save_context(th) rb_thread_t th; { VALUE *pos; size_t len; static VALUE tval; len = ruby_stack_length(&pos); th->stk_len = 0; th->stk_pos = pos; if (len > th->stk_max) { VALUE *ptr = realloc(th->stk_ptr, sizeof(VALUE) * len); if (!ptr) rb_memerror(); th->stk_ptr = ptr; th->stk_max = len; } th->stk_len = len; FLUSH_REGISTER_WINDOWS; MEMCPY(th->stk_ptr, th->stk_pos, VALUE, th->stk_len); #ifdef __ia64 th->bstr_pos = rb_gc_register_stack_start; len = (VALUE*)rb_ia64_bsp() - th->bstr_pos; th->bstr_len = 0; if (len > th->bstr_max) { VALUE *ptr = realloc(th->bstr_ptr, sizeof(VALUE) * len); if (!ptr) rb_memerror(); th->bstr_ptr = ptr; th->bstr_max = len; } th->bstr_len = len; rb_ia64_flushrs(); MEMCPY(th->bstr_ptr, th->bstr_pos, VALUE, th->bstr_len); #endif #ifdef SAVE_WIN32_EXCEPTION_LIST th->win32_exception_list = win32_get_exception_list(); #endif th->frame = ruby_frame; th->scope = ruby_scope; ruby_scope->flags |= SCOPE_DONT_RECYCLE; th->klass = ruby_class; th->wrapper = ruby_wrapper; th->cref = ruby_cref; th->dyna_vars = ruby_dyna_vars; th->block = ruby_block; th->flags &= THREAD_FLAGS_MASK; th->flags |= (rb_trap_immediate<<8) | scope_vmode; th->iter = ruby_iter; th->tag = prot_tag; th->tracing = tracing; th->errinfo = ruby_errinfo; th->last_status = rb_last_status; tval = rb_lastline_get(); rb_lastline_set(th->last_line); th->last_line = tval; tval = rb_backref_get(); rb_backref_set(th->last_match); th->last_match = tval; th->safe = ruby_safe_level; th->node = ruby_current_node; if (ruby_sandbox_save != NULL) { ruby_sandbox_save(th); } } static int rb_thread_switch(n) int n; { rb_trap_immediate = (curr_thread->flags&0x100)?1:0; switch (n) { case 0: return 0; case RESTORE_FATAL: JUMP_TAG(TAG_FATAL); break; case RESTORE_INTERRUPT: rb_interrupt(); break; case RESTORE_TRAP: rb_trap_eval(th_cmd, th_sig, th_safe); break; case RESTORE_RAISE: ruby_frame->last_func = 0; ruby_current_node = th_raise_node; rb_raise_jump(th_raise_exception); break; case RESTORE_SIGNAL: rb_thread_signal_raise(th_sig); break; case RESTORE_EXIT: ruby_errinfo = th_raise_exception; ruby_current_node = th_raise_node; if (!rb_obj_is_kind_of(ruby_errinfo, rb_eSystemExit)) { terminate_process(EXIT_FAILURE, ruby_errinfo); } rb_exc_raise(th_raise_exception); break; case RESTORE_NORMAL: default: break; } return 1; } #define THREAD_SAVE_CONTEXT(th) \ (rb_thread_switch((FLUSH_REGISTER_WINDOWS, ruby_setjmp(rb_thread_save_context(th), (th)->context)))) NORETURN(static void rb_thread_restore_context _((rb_thread_t,int))); NORETURN(NOINLINE(static void rb_thread_restore_context_0(rb_thread_t,int))); NORETURN(NOINLINE(static void stack_extend(rb_thread_t, int))); static void rb_thread_restore_context_0(rb_thread_t th, int exit) { static rb_thread_t tmp; static int ex; static VALUE tval; rb_trap_immediate = 0; /* inhibit interrupts from here */ if (ruby_sandbox_restore != NULL) { ruby_sandbox_restore(th); } ruby_frame = th->frame; ruby_scope = th->scope; ruby_class = th->klass; ruby_wrapper = th->wrapper; ruby_cref = th->cref; ruby_dyna_vars = th->dyna_vars; ruby_block = th->block; scope_vmode = th->flags&SCOPE_MASK; ruby_iter = th->iter; prot_tag = th->tag; tracing = th->tracing; ruby_errinfo = th->errinfo; rb_last_status = th->last_status; ruby_safe_level = th->safe; ruby_current_node = th->node; #ifdef SAVE_WIN32_EXCEPTION_LIST win32_set_exception_list(th->win32_exception_list); #endif tmp = th; ex = exit; FLUSH_REGISTER_WINDOWS; MEMCPY(tmp->stk_pos, tmp->stk_ptr, VALUE, tmp->stk_len); #ifdef __ia64 MEMCPY(tmp->bstr_pos, tmp->bstr_ptr, VALUE, tmp->bstr_len); #endif tval = rb_lastline_get(); rb_lastline_set(tmp->last_line); tmp->last_line = tval; tval = rb_backref_get(); rb_backref_set(tmp->last_match); tmp->last_match = tval; ruby_longjmp(tmp->context, ex); } #ifdef __ia64 #define C(a) rse_##a##0, rse_##a##1, rse_##a##2, rse_##a##3, rse_##a##4 #define E(a) rse_##a##0= rse_##a##1= rse_##a##2= rse_##a##3= rse_##a##4 static volatile int C(a), C(b), C(c), C(d), C(e); static volatile int C(f), C(g), C(h), C(i), C(j); static volatile int C(k), C(l), C(m), C(n), C(o); static volatile int C(p), C(q), C(r), C(s), C(t); int rb_dummy_false = 0; NORETURN(NOINLINE(static void register_stack_extend(rb_thread_t, int, VALUE *))); static void register_stack_extend(rb_thread_t th, int exit, VALUE *curr_bsp) { if (rb_dummy_false) { /* use registers as much as possible */ E(a) = E(b) = E(c) = E(d) = E(e) = E(f) = E(g) = E(h) = E(i) = E(j) = E(k) = E(l) = E(m) = E(n) = E(o) = E(p) = E(q) = E(r) = E(s) = E(t) = 0; E(a) = E(b) = E(c) = E(d) = E(e) = E(f) = E(g) = E(h) = E(i) = E(j) = E(k) = E(l) = E(m) = E(n) = E(o) = E(p) = E(q) = E(r) = E(s) = E(t) = 0; } if (curr_bsp < th->bstr_pos+th->bstr_len) { register_stack_extend(th, exit, (VALUE*)rb_ia64_bsp()); } stack_extend(th, exit); } #undef C #undef E #endif static void stack_extend(rb_thread_t th, int exit) { #define STACK_PAD_SIZE 1024 volatile VALUE space[STACK_PAD_SIZE], *sp = space; #if !STACK_GROW_DIRECTION if (space < rb_gc_stack_start) { /* Stack grows downward */ #endif #if STACK_GROW_DIRECTION <= 0 if (space > th->stk_pos) { # ifdef HAVE_ALLOCA sp = ALLOCA_N(VALUE, &space[0] - th->stk_pos); # else stack_extend(th, exit); # endif } #endif #if !STACK_GROW_DIRECTION } else { /* Stack grows upward */ #endif #if STACK_GROW_DIRECTION >= 0 if (&space[STACK_PAD_SIZE] < th->stk_pos + th->stk_len) { # ifdef HAVE_ALLOCA sp = ALLOCA_N(VALUE, th->stk_pos + th->stk_len - &space[STACK_PAD_SIZE]); # else stack_extend(th, exit); # endif } #endif #if !STACK_GROW_DIRECTION } #endif rb_thread_restore_context_0(th, exit); } #ifdef __ia64 #define stack_extend(th, exit) register_stack_extend(th, exit, (VALUE*)rb_ia64_bsp()) #endif static void rb_thread_restore_context(th, exit) rb_thread_t th; int exit; { if (!th->stk_ptr) rb_bug("unsaved context"); stack_extend(th, exit); } static void rb_thread_ready(th) rb_thread_t th; { th->wait_for = 0; if (th->status != THREAD_TO_KILL) { th->status = THREAD_RUNNABLE; } } static void rb_thread_die(th) rb_thread_t th; { th->thgroup = 0; th->status = THREAD_KILLED; stack_free(th); } static void rb_thread_remove(th) rb_thread_t th; { if (th->status == THREAD_KILLED) return; rb_thread_ready(th); rb_thread_die(th); th->prev->next = th->next; th->next->prev = th->prev; } static int rb_thread_dead(th) rb_thread_t th; { return th->status == THREAD_KILLED; } void rb_thread_fd_close(fd) int fd; { rb_thread_t th; FOREACH_THREAD(th) { if (((th->wait_for & WAIT_FD) && fd == th->fd) || ((th->wait_for & WAIT_SELECT) && (fd < th->fd) && (FD_ISSET(fd, &th->readfds) || FD_ISSET(fd, &th->writefds) || FD_ISSET(fd, &th->exceptfds)))) { VALUE exc = rb_exc_new2(rb_eIOError, "stream closed"); rb_thread_raise(1, &exc, th); } } END_FOREACH(th); } NORETURN(static void rb_thread_main_jump _((VALUE, int))); static void rb_thread_main_jump(err, tag) VALUE err; int tag; { curr_thread = main_thread; th_raise_exception = err; th_raise_node = ruby_current_node; rb_thread_restore_context(main_thread, tag); } NORETURN(static void rb_thread_deadlock _((void))); static void rb_thread_deadlock() { char msg[21+SIZEOF_LONG*2]; VALUE e; sprintf(msg, "Thread(0x%lx): deadlock", curr_thread->thread); e = rb_exc_new2(rb_eFatal, msg); if (curr_thread == main_thread) { rb_exc_raise(e); } rb_thread_main_jump(e, RESTORE_RAISE); } static void copy_fds(dst, src, max) fd_set *dst, *src; int max; { int n = 0; int i; for (i=0; i<=max; i++) { if (FD_ISSET(i, src)) { n = i; FD_SET(i, dst); } } } static int match_fds(dst, src, max) fd_set *dst, *src; int max; { int i; for (i=0; i<=max; i++) { if (FD_ISSET(i, src) && FD_ISSET(i, dst)) { return Qtrue; } } return Qfalse; } static int intersect_fds(src, dst, max) fd_set *src, *dst; int max; { int i, n = 0; for (i=0; i<=max; i++) { if (FD_ISSET(i, dst)) { if (FD_ISSET(i, src)) { /* Wake up only one thread per fd. */ FD_CLR(i, src); n++; } else { FD_CLR(i, dst); } } } return n; } static int find_bad_fds(dst, src, max) fd_set *dst, *src; int max; { int i, test = Qfalse; for (i=0; i<=max; i++) { if (FD_ISSET(i, src) && !FD_ISSET(i, dst)) { FD_CLR(i, src); test = Qtrue; } } return test; } void rb_thread_schedule() { rb_thread_t next; /* OK */ rb_thread_t th; rb_thread_t curr; rb_thread_t th_found = 0; int found = 0; fd_set readfds; fd_set writefds; fd_set exceptfds; struct timeval delay_tv, *delay_ptr; double delay, now; /* OK */ int n, max; int need_select = 0; int select_timeout = 0; #ifdef HAVE_NATIVETHREAD if (!is_ruby_native_thread()) { rb_bug("cross-thread violation on rb_thread_schedule()"); } #endif rb_thread_pending = 0; rb_gc_finalize_deferred(); if (curr_thread == curr_thread->next && curr_thread->status == THREAD_RUNNABLE) return; next = 0; curr = curr_thread; /* starting thread */ while (curr->status == THREAD_KILLED) { curr = curr->prev; } again: max = -1; FD_ZERO(&readfds); FD_ZERO(&writefds); FD_ZERO(&exceptfds); delay = DELAY_INFTY; now = -1.0; FOREACH_THREAD_FROM(curr, th) { if (!found && th->status <= THREAD_RUNNABLE) { found = 1; } if (th->status != THREAD_STOPPED) continue; if (th->wait_for & WAIT_JOIN) { if (rb_thread_dead(th->join)) { th->status = THREAD_RUNNABLE; found = 1; } } if (th->wait_for & WAIT_FD) { FD_SET(th->fd, &readfds); if (max < th->fd) max = th->fd; need_select = 1; } if (th->wait_for & WAIT_SELECT) { copy_fds(&readfds, &th->readfds, th->fd); copy_fds(&writefds, &th->writefds, th->fd); copy_fds(&exceptfds, &th->exceptfds, th->fd); if (max < th->fd) max = th->fd; need_select = 1; if (th->wait_for & WAIT_TIME) { select_timeout = 1; } th->select_value = 0; } if (th->wait_for & WAIT_TIME) { double th_delay; if (now < 0.0) now = timeofday(); th_delay = th->delay - now; if (th_delay <= 0.0) { th->status = THREAD_RUNNABLE; found = 1; } else if (th_delay < delay) { delay = th_delay; need_select = 1; } else if (th->delay == DELAY_INFTY) { need_select = 1; } } } END_FOREACH_FROM(curr, th); /* Do the select if needed */ if (need_select) { /* Convert delay to a timeval */ /* If a thread is runnable, just poll */ if (found) { delay_tv.tv_sec = 0; delay_tv.tv_usec = 0; delay_ptr = &delay_tv; } else if (delay == DELAY_INFTY) { delay_ptr = 0; } else { delay_tv.tv_sec = delay; delay_tv.tv_usec = (delay - (double)delay_tv.tv_sec)*1e6; delay_ptr = &delay_tv; } n = select(max+1, &readfds, &writefds, &exceptfds, delay_ptr); if (n < 0) { int e = errno; if (rb_trap_pending) rb_trap_exec(); if (e == EINTR) goto again; #ifdef ERESTART if (e == ERESTART) goto again; #endif if (e == EBADF) { int badfd = -1; int fd; int dummy; for (fd = 0; fd <= max; fd++) { if ((FD_ISSET(fd, &readfds) || FD_ISSET(fd, &writefds) || FD_ISSET(fd, &exceptfds)) && fcntl(fd, F_GETFD, &dummy) == -1 && errno == EBADF) { badfd = fd; break; } } if (badfd != -1) { FOREACH_THREAD_FROM(curr, th) { if (th->wait_for & WAIT_FD) { if (th->fd == badfd) { found = 1; th->status = THREAD_RUNNABLE; th->fd = 0; break; } } if (th->wait_for & WAIT_SELECT) { if (FD_ISSET(badfd, &th->readfds) || FD_ISSET(badfd, &th->writefds) || FD_ISSET(badfd, &th->exceptfds)) { found = 1; th->status = THREAD_RUNNABLE; th->select_value = -EBADF; break; } } } END_FOREACH_FROM(curr, th); } } else { FOREACH_THREAD_FROM(curr, th) { if (th->wait_for & WAIT_SELECT) { int v = 0; v |= find_bad_fds(&readfds, &th->readfds, th->fd); v |= find_bad_fds(&writefds, &th->writefds, th->fd); v |= find_bad_fds(&exceptfds, &th->exceptfds, th->fd); if (v) { th->select_value = n; n = max; } } } END_FOREACH_FROM(curr, th); } } if (select_timeout && n == 0) { if (now < 0.0) now = timeofday(); FOREACH_THREAD_FROM(curr, th) { if (((th->wait_for&(WAIT_SELECT|WAIT_TIME)) == (WAIT_SELECT|WAIT_TIME)) && th->delay <= now) { th->status = THREAD_RUNNABLE; th->wait_for = 0; th->select_value = 0; found = 1; intersect_fds(&readfds, &th->readfds, max); intersect_fds(&writefds, &th->writefds, max); intersect_fds(&exceptfds, &th->exceptfds, max); } } END_FOREACH_FROM(curr, th); } if (n > 0) { now = -1.0; /* Some descriptors are ready. * Choose a thread which may run next. * Don't change the status of threads which don't run next. */ FOREACH_THREAD_FROM(curr, th) { if ((th->wait_for&WAIT_FD) && FD_ISSET(th->fd, &readfds)) { th_found = th; found = 1; break; } if ((th->wait_for&WAIT_SELECT) && (match_fds(&readfds, &th->readfds, max) || match_fds(&writefds, &th->writefds, max) || match_fds(&exceptfds, &th->exceptfds, max))) { th_found = th; found = 1; break; } } END_FOREACH_FROM(curr, th); } /* The delays for some of the threads should have expired. Go through the loop once more, to check the delays. */ if (!found && delay != DELAY_INFTY) goto again; } FOREACH_THREAD_FROM(curr, th) { if (th->status == THREAD_TO_KILL) { next = th; break; } if ((th->status == THREAD_RUNNABLE || th == th_found) && th->stk_ptr) { if (!next || next->priority < th->priority) { if (th == th_found) { th_found->status = THREAD_RUNNABLE; th_found->wait_for = 0; if (th->wait_for&WAIT_FD) { th_found->fd = 0; } else { /* th->wait_for&WAIT_SELECT */ n = intersect_fds(&readfds, &th_found->readfds, max) + intersect_fds(&writefds, &th_found->writefds, max) + intersect_fds(&exceptfds, &th_found->exceptfds, max); th_found->select_value = n; } } next = th; } } } END_FOREACH_FROM(curr, th); if (!next) { /* raise fatal error to main thread */ curr_thread->node = ruby_current_node; if (curr->next == curr) { TRAP_BEG; pause(); TRAP_END; } FOREACH_THREAD_FROM(curr, th) { warn_printf("deadlock 0x%lx: %s:", th->thread, thread_status_name(th->status)); if (th->wait_for & WAIT_FD) warn_printf("F(%d)", th->fd); if (th->wait_for & WAIT_SELECT) warn_printf("S"); if (th->wait_for & WAIT_TIME) warn_printf("T(%f)", th->delay); if (th->wait_for & WAIT_JOIN) warn_printf("J(0x%lx)", th->join ? th->join->thread : 0); if (th->wait_for & WAIT_PID) warn_printf("P"); if (!th->wait_for) warn_printf("-"); warn_printf(" %s - %s:%d\n", th==main_thread ? "(main)" : "", th->node->nd_file, nd_line(th->node)); } END_FOREACH_FROM(curr, th); next = main_thread; rb_thread_ready(next); next->status = THREAD_TO_KILL; if (!rb_thread_dead(curr_thread)) { rb_thread_save_context(curr_thread); } rb_thread_deadlock(); } next->wait_for = 0; if (next->status == THREAD_RUNNABLE && next == curr_thread) { return; } /* context switch */ if (curr == curr_thread) { if (THREAD_SAVE_CONTEXT(curr)) { return; } } curr_thread = next; if (next->status == THREAD_TO_KILL) { if (!(next->flags & THREAD_TERMINATING)) { next->flags |= THREAD_TERMINATING; /* terminate; execute ensure-clause if any */ rb_thread_restore_context(next, RESTORE_FATAL); } } rb_thread_restore_context(next, RESTORE_NORMAL); } void rb_thread_wait_fd(fd) int fd; { if (rb_thread_critical) return; if (ruby_in_compile) return; if (curr_thread == curr_thread->next) return; if (curr_thread->status == THREAD_TO_KILL) return; curr_thread->status = THREAD_STOPPED; curr_thread->fd = fd; curr_thread->wait_for = WAIT_FD; rb_thread_schedule(); } int rb_thread_fd_writable(fd) int fd; { if (rb_thread_critical) return Qtrue; if (curr_thread == curr_thread->next) return Qtrue; if (curr_thread->status == THREAD_TO_KILL) return Qtrue; if (curr_thread->status == THREAD_KILLED) return Qtrue; curr_thread->status = THREAD_STOPPED; FD_ZERO(&curr_thread->readfds); FD_ZERO(&curr_thread->writefds); FD_SET(fd, &curr_thread->writefds); FD_ZERO(&curr_thread->exceptfds); curr_thread->fd = fd+1; curr_thread->wait_for = WAIT_SELECT; rb_thread_schedule(); return Qfalse; } void rb_thread_wait_for(time) struct timeval time; { double date; if (rb_thread_critical || curr_thread == curr_thread->next || curr_thread->status == THREAD_TO_KILL) { int n; int thr_critical = rb_thread_critical; #ifndef linux double d, limit; limit = timeofday()+(double)time.tv_sec+(double)time.tv_usec*1e-6; #endif for (;;) { rb_thread_critical = Qtrue; TRAP_BEG; n = select(0, 0, 0, 0, &time); rb_thread_critical = thr_critical; TRAP_END; if (n == 0) return; if (n < 0) { switch (errno) { case EINTR: #ifdef ERESTART case ERESTART: #endif break; default: rb_sys_fail("sleep"); } } #ifndef linux d = limit - timeofday(); time.tv_sec = (int)d; time.tv_usec = (int)((d - (int)d)*1e6); if (time.tv_usec < 0) { time.tv_usec += (long)1e6; time.tv_sec -= 1; } if (time.tv_sec < 0) return; #endif } } date = timeofday() + (double)time.tv_sec + (double)time.tv_usec*1e-6; curr_thread->status = THREAD_STOPPED; curr_thread->delay = date; curr_thread->wait_for = WAIT_TIME; rb_thread_schedule(); } void rb_thread_sleep_forever _((void)); int rb_thread_alone() { return curr_thread == curr_thread->next; } int rb_thread_select(max, read, write, except, timeout) int max; fd_set *read, *write, *except; struct timeval *timeout; { #ifndef linux double limit; #endif int n; if (!read && !write && !except) { if (!timeout) { rb_thread_sleep_forever(); return 0; } rb_thread_wait_for(*timeout); return 0; } #ifndef linux if (timeout) { limit = timeofday()+ (double)timeout->tv_sec+(double)timeout->tv_usec*1e-6; } #endif if (rb_thread_critical || curr_thread == curr_thread->next || curr_thread->status == THREAD_TO_KILL) { #ifndef linux struct timeval tv, *tvp = timeout; if (timeout) { tv = *timeout; tvp = &tv; } #else struct timeval *const tvp = timeout; #endif for (;;) { TRAP_BEG; n = select(max, read, write, except, tvp); TRAP_END; if (n < 0) { switch (errno) { case EINTR: #ifdef ERESTART case ERESTART: #endif #ifndef linux if (timeout) { double d = limit - timeofday(); tv.tv_sec = (unsigned int)d; tv.tv_usec = (long)((d-(double)tv.tv_sec)*1e6); if (tv.tv_sec < 0) tv.tv_sec = 0; if (tv.tv_usec < 0) tv.tv_usec = 0; } #endif continue; default: break; } } return n; } } curr_thread->status = THREAD_STOPPED; if (read) curr_thread->readfds = *read; else FD_ZERO(&curr_thread->readfds); if (write) curr_thread->writefds = *write; else FD_ZERO(&curr_thread->writefds); if (except) curr_thread->exceptfds = *except; else FD_ZERO(&curr_thread->exceptfds); curr_thread->fd = max; curr_thread->wait_for = WAIT_SELECT; if (timeout) { curr_thread->delay = timeofday() + (double)timeout->tv_sec + (double)timeout->tv_usec*1e-6; curr_thread->wait_for |= WAIT_TIME; } rb_thread_schedule(); if (read) *read = curr_thread->readfds; if (write) *write = curr_thread->writefds; if (except) *except = curr_thread->exceptfds; if (curr_thread->select_value < 0) { errno = -curr_thread->select_value; return -1; } return curr_thread->select_value; } static int rb_thread_join0 _((rb_thread_t, double)); int rb_thread_join _((VALUE, double)); static int rb_thread_join0(th, limit) rb_thread_t th; double limit; { enum rb_thread_status last_status = THREAD_RUNNABLE; if (rb_thread_critical) rb_thread_deadlock(); if (!rb_thread_dead(th)) { if (th == curr_thread) rb_raise(rb_eThreadError, "thread 0x%lx tried to join itself", th->thread); if ((th->wait_for & WAIT_JOIN) && th->join == curr_thread) rb_raise(rb_eThreadError, "Thread#join: deadlock 0x%lx - mutual join(0x%lx)", curr_thread->thread, th->thread); if (curr_thread->status == THREAD_TO_KILL) last_status = THREAD_TO_KILL; if (limit == 0) return Qfalse; curr_thread->status = THREAD_STOPPED; curr_thread->join = th; curr_thread->wait_for = WAIT_JOIN; curr_thread->delay = timeofday() + limit; if (limit < DELAY_INFTY) curr_thread->wait_for |= WAIT_TIME; rb_thread_schedule(); curr_thread->status = last_status; if (!rb_thread_dead(th)) return Qfalse; } if (!NIL_P(th->errinfo) && (th->flags & RAISED_EXCEPTION)) { VALUE oldbt = get_backtrace(th->errinfo); VALUE errat = make_backtrace(); VALUE errinfo = rb_obj_dup(th->errinfo); if (TYPE(oldbt) == T_ARRAY && RARRAY(oldbt)->len > 0) { rb_ary_unshift(errat, rb_ary_entry(oldbt, 0)); } set_backtrace(errinfo, errat); rb_exc_raise(errinfo); } return Qtrue; } int rb_thread_join(thread, limit) VALUE thread; double limit; { if (limit < 0) limit = DELAY_INFTY; return rb_thread_join0(rb_thread_check(thread), limit); } /* * call-seq: * thr.join => thr * thr.join(limit) => thr * * The calling thread will suspend execution and run thr. Does not * return until thr exits or until limit seconds have passed. If * the time limit expires, nil will be returned, otherwise * thr is returned. * * Any threads not joined will be killed when the main program exits. If * thr had previously raised an exception and the * abort_on_exception and $DEBUG flags are not set * (so the exception has not yet been processed) it will be processed at this * time. * * a = Thread.new { print "a"; sleep(10); print "b"; print "c" } * x = Thread.new { print "x"; Thread.pass; print "y"; print "z" } * x.join # Let x thread finish, a will be killed on exit. * * produces: * * axyz * * The following example illustrates the limit parameter. * * y = Thread.new { 4.times { sleep 0.1; puts 'tick... ' }} * puts "Waiting" until y.join(0.15) * * produces: * * tick... * Waiting * tick... * Waitingtick... * * * tick... */ static VALUE rb_thread_join_m(argc, argv, thread) int argc; VALUE *argv; VALUE thread; { VALUE limit; double delay = DELAY_INFTY; rb_scan_args(argc, argv, "01", &limit); if (!NIL_P(limit)) delay = rb_num2dbl(limit); if (!rb_thread_join0(rb_thread_check(thread), delay)) return Qnil; return thread; } /* * call-seq: * Thread.current => thread * * Returns the currently executing thread. * * Thread.current #=> # */ VALUE rb_thread_current() { return curr_thread->thread; } /* * call-seq: * Thread.main => thread * * Returns the main thread for the process. * * Thread.main #=> # */ VALUE rb_thread_main() { return main_thread->thread; } /* * call-seq: * Thread.list => array * * Returns an array of Thread objects for all threads that are * either runnable or stopped. * * Thread.new { sleep(200) } * Thread.new { 1000000.times {|i| i*i } } * Thread.new { Thread.stop } * Thread.list.each {|t| p t} * * produces: * * # * # * # * # */ VALUE rb_thread_list() { rb_thread_t th; VALUE ary = rb_ary_new(); FOREACH_THREAD(th) { switch (th->status) { case THREAD_RUNNABLE: case THREAD_STOPPED: case THREAD_TO_KILL: rb_ary_push(ary, th->thread); default: break; } } END_FOREACH(th); return ary; } /* * call-seq: * thr.wakeup => thr * * Marks thr as eligible for scheduling (it may still remain blocked on * I/O, however). Does not invoke the scheduler (see Thread#run). * * c = Thread.new { Thread.stop; puts "hey!" } * c.wakeup * * produces: * * hey! */ VALUE rb_thread_wakeup(thread) VALUE thread; { if (!RTEST(rb_thread_wakeup_alive(thread))) rb_raise(rb_eThreadError, "killed thread"); return thread; } VALUE rb_thread_wakeup_alive(thread) VALUE thread; { rb_thread_t th = rb_thread_check(thread); if (th->status == THREAD_KILLED) return Qnil; rb_thread_ready(th); return thread; } /* * call-seq: * thr.run => thr * * Wakes up thr, making it eligible for scheduling. If not in a critical * section, then invokes the scheduler. * * a = Thread.new { puts "a"; Thread.stop; puts "c" } * Thread.pass * puts "Got here" * a.run * a.join * * produces: * * a * Got here * c */ VALUE rb_thread_run(thread) VALUE thread; { rb_thread_wakeup(thread); if (!rb_thread_critical) rb_thread_schedule(); return thread; } static void rb_kill_thread(th, flags) rb_thread_t th; int flags; { if (th != curr_thread && th->safe < 4) { rb_secure(4); } if (th->status == THREAD_TO_KILL || th->status == THREAD_KILLED) return; if (th == th->next || th == main_thread) rb_exit(EXIT_SUCCESS); rb_thread_ready(th); th->flags |= flags; th->status = THREAD_TO_KILL; if (!rb_thread_critical) rb_thread_schedule(); } /* * call-seq: * thr.exit => thr * thr.kill => thr * thr.terminate => thr * * Terminates thr and schedules another thread to be run, returning * the terminated Thread. If this is the main thread, or the * last thread, exits the process. */ VALUE rb_thread_kill(thread) VALUE thread; { rb_thread_t th = rb_thread_check(thread); rb_kill_thread(th, 0); return thread; } /* * call-seq: * thr.exit! => thr * thr.kill! => thr * thr.terminate! => thr * * Terminates thr without calling ensure clauses and schedules * another thread to be run, returning the terminated Thread. * If this is the main thread, or the last thread, exits the process. * * See Thread#exit for the safer version. */ static VALUE rb_thread_kill_bang(thread) VALUE thread; { rb_thread_t th = rb_thread_check(thread); rb_kill_thread(th, THREAD_NO_ENSURE); return thread; } /* * call-seq: * Thread.kill(thread) => thread * * Causes the given thread to exit (see Thread::exit). * * count = 0 * a = Thread.new { loop { count += 1 } } * sleep(0.1) #=> 0 * Thread.kill(a) #=> # * count #=> 93947 * a.alive? #=> false */ static VALUE rb_thread_s_kill(obj, th) VALUE obj, th; { return rb_thread_kill(th); } /* * call-seq: * Thread.exit => thread * * Terminates the currently running thread and schedules another thread to be * run. If this thread is already marked to be killed, exit * returns the Thread. If this is the main thread, or the last * thread, exit the process. */ static VALUE rb_thread_exit() { return rb_thread_kill(curr_thread->thread); } /* * call-seq: * Thread.pass => nil * * Invokes the thread scheduler to pass execution to another thread. * * a = Thread.new { print "a"; Thread.pass; * print "b"; Thread.pass; * print "c" } * b = Thread.new { print "x"; Thread.pass; * print "y"; Thread.pass; * print "z" } * a.join * b.join * * produces: * * axbycz */ static VALUE rb_thread_pass() { rb_thread_schedule(); return Qnil; } /* * call-seq: * Thread.stop => nil * * Stops execution of the current thread, putting it into a ``sleep'' state, * and schedules execution of another thread. Resets the ``critical'' condition * to false. * * a = Thread.new { print "a"; Thread.stop; print "c" } * Thread.pass * print "b" * a.run * a.join * * produces: * * abc */ VALUE rb_thread_stop() { enum rb_thread_status last_status = THREAD_RUNNABLE; rb_thread_critical = 0; if (curr_thread == curr_thread->next) { rb_raise(rb_eThreadError, "stopping only thread\n\tnote: use sleep to stop forever"); } if (curr_thread->status == THREAD_TO_KILL) last_status = THREAD_TO_KILL; curr_thread->status = THREAD_STOPPED; rb_thread_schedule(); curr_thread->status = last_status; return Qnil; } struct timeval rb_time_timeval(); void rb_thread_polling() { if (curr_thread != curr_thread->next) { curr_thread->status = THREAD_STOPPED; curr_thread->delay = timeofday() + (double)0.06; curr_thread->wait_for = WAIT_TIME; rb_thread_schedule(); } } void rb_thread_sleep(sec) int sec; { if (curr_thread == curr_thread->next) { TRAP_BEG; sleep(sec); TRAP_END; return; } rb_thread_wait_for(rb_time_timeval(INT2FIX(sec))); } void rb_thread_sleep_forever() { int thr_critical = rb_thread_critical; if (curr_thread == curr_thread->next || curr_thread->status == THREAD_TO_KILL) { rb_thread_critical = Qtrue; TRAP_BEG; pause(); rb_thread_critical = thr_critical; TRAP_END; return; } curr_thread->delay = DELAY_INFTY; curr_thread->wait_for = WAIT_TIME; curr_thread->status = THREAD_STOPPED; rb_thread_schedule(); } /* * call-seq: * thr.priority => integer * * Returns the priority of thr. Default is inherited from the * current thread which creating the new thread, or zero for the * initial main thread; higher-priority threads will run before * lower-priority threads. * * Thread.current.priority #=> 0 */ static VALUE rb_thread_priority(thread) VALUE thread; { return INT2NUM(rb_thread_check(thread)->priority); } /* * call-seq: * thr.priority= integer => thr * * Sets the priority of thr to integer. Higher-priority threads * will run before lower-priority threads. * * count1 = count2 = 0 * a = Thread.new do * loop { count1 += 1 } * end * a.priority = -1 * * b = Thread.new do * loop { count2 += 1 } * end * b.priority = -2 * sleep 1 #=> 1 * Thread.critical = 1 * count1 #=> 622504 * count2 #=> 5832 */ static VALUE rb_thread_priority_set(thread, prio) VALUE thread, prio; { rb_thread_t th; rb_secure(4); th = rb_thread_check(thread); th->priority = NUM2INT(prio); rb_thread_schedule(); return prio; } /* * call-seq: * thr.safe_level => integer * * Returns the safe level in effect for thr. Setting thread-local safe * levels can help when implementing sandboxes which run insecure code. * * thr = Thread.new { $SAFE = 3; sleep } * Thread.current.safe_level #=> 0 * thr.safe_level #=> 3 */ static VALUE rb_thread_safe_level(thread) VALUE thread; { rb_thread_t th; th = rb_thread_check(thread); if (th == curr_thread) { return INT2NUM(ruby_safe_level); } return INT2NUM(th->safe); } static int ruby_thread_abort; static VALUE thgroup_default; /* * call-seq: * Thread.abort_on_exception => true or false * * Returns the status of the global ``abort on exception'' condition. The * default is false. When set to true, or if the * global $DEBUG flag is true (perhaps because the * command line option -d was specified) all threads will abort * (the process will exit(0)) if an exception is raised in any * thread. See also Thread::abort_on_exception=. */ static VALUE rb_thread_s_abort_exc() { return ruby_thread_abort?Qtrue:Qfalse; } /* * call-seq: * Thread.abort_on_exception= boolean => true or false * * When set to true, all threads will abort if an exception is * raised. Returns the new state. * * Thread.abort_on_exception = true * t1 = Thread.new do * puts "In new thread" * raise "Exception from thread" * end * sleep(1) * puts "not reached" * * produces: * * In new thread * prog.rb:4: Exception from thread (RuntimeError) * from prog.rb:2:in `initialize' * from prog.rb:2:in `new' * from prog.rb:2 */ static VALUE rb_thread_s_abort_exc_set(self, val) VALUE self, val; { rb_secure(4); ruby_thread_abort = RTEST(val); return val; } /* * call-seq: * thr.abort_on_exception => true or false * * Returns the status of the thread-local ``abort on exception'' condition for * thr. The default is false. See also * Thread::abort_on_exception=. */ static VALUE rb_thread_abort_exc(thread) VALUE thread; { return rb_thread_check(thread)->abort?Qtrue:Qfalse; } /* * call-seq: * thr.abort_on_exception= boolean => true or false * * When set to true, causes all threads (including the main * program) to abort if an exception is raised in thr. The process will * effectively exit(0). */ static VALUE rb_thread_abort_exc_set(thread, val) VALUE thread, val; { rb_secure(4); rb_thread_check(thread)->abort = RTEST(val); return val; } enum rb_thread_status rb_thread_status(thread) VALUE thread; { rb_thread_t th = rb_thread_check(thread); return th->status; } /* * call-seq: * thr.group => thgrp or nil * * Returns the ThreadGroup which contains thr, or nil if * the thread is not a member of any group. * * Thread.main.group #=> # */ VALUE rb_thread_group(thread) VALUE thread; { VALUE group = rb_thread_check(thread)->thgroup; if (!group) { group = Qnil; } return group; } #ifdef __ia64 # define IA64_INIT(x) x #else # define IA64_INIT(x) #endif #define THREAD_ALLOC(th) do {\ th = ALLOC(struct rb_thread);\ \ th->next = 0;\ th->prev = 0;\ \ th->status = THREAD_RUNNABLE;\ th->result = 0;\ th->flags = 0;\ \ th->stk_ptr = 0;\ th->stk_len = 0;\ th->stk_max = 0;\ th->wait_for = 0;\ IA64_INIT(th->bstr_ptr = 0);\ IA64_INIT(th->bstr_len = 0);\ IA64_INIT(th->bstr_max = 0);\ FD_ZERO(&th->readfds);\ FD_ZERO(&th->writefds);\ FD_ZERO(&th->exceptfds);\ th->delay = 0.0;\ th->join = 0;\ \ th->frame = 0;\ th->scope = 0;\ th->klass = 0;\ th->wrapper = 0;\ th->cref = ruby_cref;\ th->dyna_vars = ruby_dyna_vars;\ th->block = 0;\ th->iter = 0;\ th->tag = 0;\ th->tracing = 0;\ th->errinfo = Qnil;\ th->last_status = 0;\ th->last_line = 0;\ th->last_match = Qnil;\ th->abort = 0;\ th->priority = 0;\ th->thgroup = thgroup_default;\ th->locals = 0;\ th->thread = 0;\ if (curr_thread == 0) {\ th->sandbox = Qnil;\ } else {\ th->sandbox = curr_thread->sandbox;\ }\ } while (0) static rb_thread_t rb_thread_alloc(klass) VALUE klass; { rb_thread_t th; struct RVarmap *vars; THREAD_ALLOC(th); th->thread = Data_Wrap_Struct(klass, thread_mark, thread_free, th); for (vars = th->dyna_vars; vars; vars = vars->next) { if (FL_TEST(vars, DVAR_DONT_RECYCLE)) break; FL_SET(vars, DVAR_DONT_RECYCLE); } return th; } static int thread_init; #if defined(_THREAD_SAFE) static void catch_timer(sig) int sig; { #if !defined(POSIX_SIGNAL) && !defined(BSD_SIGNAL) signal(sig, catch_timer); #endif /* cause EINTR */ } static int time_thread_alive_p = 0; static pthread_t time_thread; static void* thread_timer(dummy) void *dummy; { #ifdef _THREAD_SAFE #define test_cancel() pthread_testcancel() #else #define test_cancel() /* void */ #endif sigset_t all_signals; sigfillset(&all_signals); pthread_sigmask(SIG_BLOCK, &all_signals, 0); for (;;) { #ifdef HAVE_NANOSLEEP struct timespec req, rem; test_cancel(); req.tv_sec = 0; req.tv_nsec = 10000000; nanosleep(&req, &rem); #else struct timeval tv; test_cancel(); tv.tv_sec = 0; tv.tv_usec = 10000; select(0, NULL, NULL, NULL, &tv); #endif if (!rb_thread_critical) { rb_thread_pending = 1; if (rb_trap_immediate) { pthread_kill(ruby_thid, SIGVTALRM); } } } #undef test_cancel } void rb_thread_start_timer() { } void rb_thread_stop_timer() { } void rb_child_atfork() { time_thread_alive_p = 0; } void rb_thread_cancel_timer() { #ifdef _THREAD_SAFE if( time_thread_alive_p ) { pthread_cancel( time_thread ); pthread_join( time_thread, NULL ); time_thread_alive_p = 0; } thread_init = 0; #endif } #elif defined(HAVE_SETITIMER) static void catch_timer(sig) int sig; { #if !defined(POSIX_SIGNAL) && !defined(BSD_SIGNAL) signal(sig, catch_timer); #endif if (!rb_thread_critical) { rb_thread_pending = 1; } /* cause EINTR */ } void rb_thread_start_timer() { struct itimerval tval; if (!thread_init) return; tval.it_interval.tv_sec = 0; tval.it_interval.tv_usec = 10000; tval.it_value = tval.it_interval; setitimer(ITIMER_VIRTUAL, &tval, NULL); } void rb_thread_stop_timer() { struct itimerval tval; if (!thread_init) return; tval.it_interval.tv_sec = 0; tval.it_interval.tv_usec = 0; tval.it_value = tval.it_interval; setitimer(ITIMER_VIRTUAL, &tval, NULL); } void rb_thread_cancel_timer() { } #else /* !(_THREAD_SAFE || HAVE_SETITIMER) */ int rb_thread_tick = THREAD_TICK; void rb_thread_cancel_timer() { } #endif static VALUE rb_thread_start_0(fn, arg, th) VALUE (*fn)(); void *arg; rb_thread_t th; { volatile rb_thread_t th_save = th; volatile VALUE thread = th->thread; struct BLOCK *volatile saved_block = 0; enum rb_thread_status status; int state; if (OBJ_FROZEN(curr_thread->thgroup)) { rb_raise(rb_eThreadError, "can't start a new thread (frozen ThreadGroup)"); } if (!thread_init) { thread_init = 1; #if defined(HAVE_SETITIMER) || defined(_THREAD_SAFE) #if defined(POSIX_SIGNAL) posix_signal(SIGVTALRM, catch_timer); #else signal(SIGVTALRM, catch_timer); #endif #ifdef _THREAD_SAFE pthread_create(&time_thread, 0, thread_timer, 0); time_thread_alive_p = 1; pthread_atfork(0, 0, rb_child_atfork); #else rb_thread_start_timer(); #endif #endif } if (THREAD_SAVE_CONTEXT(curr_thread)) { return thread; } if (ruby_block) { /* should nail down higher blocks */ struct BLOCK dummy; dummy.prev = ruby_block; blk_copy_prev(&dummy); saved_block = ruby_block = dummy.prev; } scope_dup(ruby_scope); if (!th->next) { /* merge in thread list */ th->prev = curr_thread; curr_thread->next->prev = th; th->next = curr_thread->next; curr_thread->next = th; th->priority = curr_thread->priority; th->thgroup = curr_thread->thgroup; } PUSH_TAG(PROT_THREAD); if ((state = EXEC_TAG()) == 0) { if (THREAD_SAVE_CONTEXT(th) == 0) { curr_thread = th; th->result = (*fn)(arg, th); } th = th_save; } else if (TAG_DST()) { th = th_save; th->result = prot_tag->retval; } POP_TAG(); status = th->status; if (th == main_thread) ruby_stop(state); rb_thread_remove(th); if (saved_block) { blk_free(saved_block); } if (state && status != THREAD_TO_KILL && !NIL_P(ruby_errinfo)) { th->flags |= RAISED_EXCEPTION; if (state == TAG_FATAL) { /* fatal error within this thread, need to stop whole script */ main_thread->errinfo = ruby_errinfo; rb_thread_cleanup(); } else if (rb_obj_is_kind_of(ruby_errinfo, rb_eSystemExit)) { if (th->safe >= 4) { char buf[32]; sprintf(buf, "Insecure exit at level %d", th->safe); th->errinfo = rb_exc_new2(rb_eSecurityError, buf); } else { /* delegate exception to main_thread */ rb_thread_main_jump(ruby_errinfo, RESTORE_RAISE); } } else if (th->safe < 4 && (ruby_thread_abort || th->abort || RTEST(ruby_debug))) { /* exit on main_thread */ error_print(); rb_thread_main_jump(ruby_errinfo, RESTORE_EXIT); } else { th->errinfo = ruby_errinfo; } } rb_thread_schedule(); ruby_stop(0); /* last thread termination */ return 0; /* not reached */ } VALUE rb_thread_create(fn, arg) VALUE (*fn)(); void *arg; { Init_stack((void *)&arg); return rb_thread_start_0(fn, arg, rb_thread_alloc(rb_cThread)); } static VALUE rb_thread_yield(arg, th) VALUE arg; rb_thread_t th; { const ID *tbl; scope_dup(ruby_block->scope); tbl = ruby_scope->local_tbl; if (tbl) { int n = *tbl++; for (tbl += 2, n -= 2; n > 0; --n) { /* skip first 2 ($_ and $~) */ ID id = *tbl++; if (id != 0 && !rb_is_local_id(id)) /* push flip states */ rb_dvar_push(id, Qfalse); } } rb_dvar_push('_', Qnil); rb_dvar_push('~', Qnil); ruby_block->dyna_vars = ruby_dyna_vars; return rb_yield_0(arg, 0, 0, YIELD_LAMBDA_CALL, Qtrue); } /* * call-seq: * Thread.new([arg]*) {|args| block } => thread * * Creates and runs a new thread to execute the instructions given in * block. Any arguments passed to Thread::new are passed * into the block. * * x = Thread.new { sleep 0.1; print "x"; print "y"; print "z" } * a = Thread.new { print "a"; print "b"; sleep 0.2; print "c" } * x.join # Let the threads finish before * a.join # main thread exits... * * produces: * * abxyzc */ static VALUE rb_thread_s_new(argc, argv, klass) int argc; VALUE *argv; VALUE klass; { rb_thread_t th = rb_thread_alloc(klass); volatile VALUE *pos; pos = th->stk_pos; rb_obj_call_init(th->thread, argc, argv); if (th->stk_pos == 0) { rb_raise(rb_eThreadError, "uninitialized thread - check `%s#initialize'", rb_class2name(klass)); } return th->thread; } /* * call-seq: * Thread.new([arg]*) {|args| block } => thread * * Creates and runs a new thread to execute the instructions given in * block. Any arguments passed to Thread::new are passed * into the block. * * x = Thread.new { sleep 0.1; print "x"; print "y"; print "z" } * a = Thread.new { print "a"; print "b"; sleep 0.2; print "c" } * x.join # Let the threads finish before * a.join # main thread exits... * * produces: * * abxyzc */ static VALUE rb_thread_initialize(thread, args) VALUE thread, args; { rb_thread_t th; if (!rb_block_given_p()) { rb_raise(rb_eThreadError, "must be called with a block"); } th = rb_thread_check(thread); if (th->stk_max) { NODE *node = th->node; if (!node) { rb_raise(rb_eThreadError, "already initialized thread"); } rb_raise(rb_eThreadError, "already initialized thread - %s:%d", node->nd_file, nd_line(node)); } return rb_thread_start_0(rb_thread_yield, args, th); } /* * call-seq: * Thread.start([args]*) {|args| block } => thread * Thread.fork([args]*) {|args| block } => thread * * Basically the same as Thread::new. However, if class * Thread is subclassed, then calling start in that * subclass will not invoke the subclass's initialize method. */ static VALUE rb_thread_start(klass, args) VALUE klass, args; { if (!rb_block_given_p()) { rb_raise(rb_eThreadError, "must be called with a block"); } return rb_thread_start_0(rb_thread_yield, args, rb_thread_alloc(klass)); } /* * call-seq: * thr.value => obj * * Waits for thr to complete (via Thread#join) and returns * its value. * * a = Thread.new { 2 + 2 } * a.value #=> 4 */ static VALUE rb_thread_value(thread) VALUE thread; { rb_thread_t th = rb_thread_check(thread); while (!rb_thread_join0(th, DELAY_INFTY)); return th->result; } /* * call-seq: * thr.status => string, false or nil * * Returns the status of thr: ``sleep'' if thr is * sleeping or waiting on I/O, ``run'' if thr is executing, * ``aborting'' if thr is aborting, false if * thr terminated normally, and nil if thr * terminated with an exception. * * a = Thread.new { raise("die now") } * b = Thread.new { Thread.stop } * c = Thread.new { Thread.exit } * d = Thread.new { sleep } * Thread.critical = true * d.kill #=> # * a.status #=> nil * b.status #=> "sleep" * c.status #=> false * d.status #=> "aborting" * Thread.current.status #=> "run" */ static VALUE rb_thread_status_name(thread) VALUE thread; { rb_thread_t th = rb_thread_check(thread); if (rb_thread_dead(th)) { if (!NIL_P(th->errinfo) && (th->flags & RAISED_EXCEPTION)) return Qnil; return Qfalse; } return rb_str_new2(thread_status_name(th->status)); } /* * call-seq: * thr.alive? => true or false * * Returns true if thr is running or sleeping. * * thr = Thread.new { } * thr.join #=> # * Thread.current.alive? #=> true * thr.alive? #=> false */ VALUE rb_thread_alive_p(thread) VALUE thread; { rb_thread_t th = rb_thread_check(thread); if (rb_thread_dead(th)) return Qfalse; return Qtrue; } /* * call-seq: * thr.stop? => true or false * * Returns true if thr is dead or sleeping. * * a = Thread.new { Thread.stop } * b = Thread.current * a.stop? #=> true * b.stop? #=> false */ static VALUE rb_thread_stop_p(thread) VALUE thread; { rb_thread_t th = rb_thread_check(thread); if (rb_thread_dead(th)) return Qtrue; if (th->status == THREAD_STOPPED) return Qtrue; return Qfalse; } static void rb_thread_wait_other_threads() { rb_thread_t th; int found; /* wait other threads to terminate */ while (curr_thread != curr_thread->next) { found = 0; FOREACH_THREAD(th) { if (th != curr_thread && th->status != THREAD_STOPPED) { found = 1; break; } } END_FOREACH(th); if (!found) return; rb_thread_schedule(); } } static void rb_thread_cleanup() { rb_thread_t curr, th; curr = curr_thread; while (curr->status == THREAD_KILLED) { curr = curr->prev; } FOREACH_THREAD_FROM(curr, th) { if (th->status != THREAD_KILLED) { rb_thread_ready(th); if (th != main_thread) { th->thgroup = 0; th->priority = 0; th->status = THREAD_TO_KILL; RDATA(th->thread)->dfree = NULL; } } } END_FOREACH_FROM(curr, th); } int rb_thread_critical; /* * call-seq: * Thread.critical => true or false * * Returns the status of the global ``thread critical'' condition. */ static VALUE rb_thread_critical_get() { return rb_thread_critical?Qtrue:Qfalse; } /* * call-seq: * Thread.critical= boolean => true or false * * Sets the status of the global ``thread critical'' condition and returns * it. When set to true, prohibits scheduling of any existing * thread. Does not block new threads from being created and run. Certain * thread operations (such as stopping or killing a thread, sleeping in the * current thread, and raising an exception) may cause a thread to be scheduled * even when in a critical section. Thread::critical is not * intended for daily use: it is primarily there to support folks writing * threading libraries. */ static VALUE rb_thread_critical_set(obj, val) VALUE obj, val; { rb_thread_critical = RTEST(val); return val; } void rb_thread_interrupt() { rb_thread_critical = 0; rb_thread_ready(main_thread); if (curr_thread == main_thread) { rb_interrupt(); } if (!rb_thread_dead(curr_thread)) { if (THREAD_SAVE_CONTEXT(curr_thread)) { return; } } curr_thread = main_thread; rb_thread_restore_context(curr_thread, RESTORE_INTERRUPT); } void rb_thread_signal_raise(sig) int sig; { rb_thread_critical = 0; if (curr_thread == main_thread) { VALUE argv[1]; rb_thread_ready(curr_thread); argv[0] = INT2FIX(sig); rb_exc_raise(rb_class_new_instance(1, argv, rb_eSignal)); } rb_thread_ready(main_thread); if (!rb_thread_dead(curr_thread)) { if (THREAD_SAVE_CONTEXT(curr_thread)) { return; } } th_sig = sig; curr_thread = main_thread; rb_thread_restore_context(curr_thread, RESTORE_SIGNAL); } void rb_thread_trap_eval(cmd, sig, safe) VALUE cmd; int sig, safe; { rb_thread_critical = 0; if (curr_thread == main_thread) { rb_trap_eval(cmd, sig, safe); return; } if (!rb_thread_dead(curr_thread)) { if (THREAD_SAVE_CONTEXT(curr_thread)) { return; } } th_cmd = cmd; th_sig = sig; th_safe = safe; curr_thread = main_thread; rb_thread_restore_context(curr_thread, RESTORE_TRAP); } void rb_thread_signal_exit() { VALUE args[2]; rb_thread_critical = 0; if (curr_thread == main_thread) { rb_thread_ready(curr_thread); rb_exit(EXIT_SUCCESS); } args[0] = INT2NUM(EXIT_SUCCESS); args[1] = rb_str_new2("exit"); rb_thread_ready(main_thread); if (!rb_thread_dead(curr_thread)) { if (THREAD_SAVE_CONTEXT(curr_thread)) { return; } } rb_thread_main_jump(rb_class_new_instance(2, args, rb_eSystemExit), RESTORE_EXIT); } static VALUE rb_thread_raise(argc, argv, th) int argc; VALUE *argv; rb_thread_t th; { volatile rb_thread_t th_save = th; VALUE exc; if (!th->next) { rb_raise(rb_eArgError, "unstarted thread"); } if (rb_thread_dead(th)) return Qnil; exc = rb_make_exception(argc, argv); if (curr_thread == th) { rb_raise_jump(exc); } if (!rb_thread_dead(curr_thread)) { if (THREAD_SAVE_CONTEXT(curr_thread)) { return th_save->thread; } } rb_thread_ready(th); curr_thread = th; th_raise_exception = exc; th_raise_node = ruby_current_node; rb_thread_restore_context(curr_thread, RESTORE_RAISE); return Qnil; /* not reached */ } /* * call-seq: * thr.raise(exception) * * Raises an exception (see Kernel::raise) from thr. The * caller does not have to be thr. * * Thread.abort_on_exception = true * a = Thread.new { sleep(200) } * a.raise("Gotcha") * * produces: * * prog.rb:3: Gotcha (RuntimeError) * from prog.rb:2:in `initialize' * from prog.rb:2:in `new' * from prog.rb:2 */ static VALUE rb_thread_raise_m(argc, argv, thread) int argc; VALUE *argv; VALUE thread; { rb_thread_t th = rb_thread_check(thread); if (ruby_safe_level > th->safe) { rb_secure(4); } rb_thread_raise(argc, argv, th); return Qnil; /* not reached */ } VALUE rb_thread_local_aref(thread, id) VALUE thread; ID id; { rb_thread_t th; VALUE val; th = rb_thread_check(thread); if (ruby_safe_level >= 4 && th != curr_thread) { rb_raise(rb_eSecurityError, "Insecure: thread locals"); } if (!th->locals) return Qnil; if (st_lookup(th->locals, id, &val)) { return val; } return Qnil; } /* * call-seq: * thr[sym] => obj or nil * * Attribute Reference---Returns the value of a thread-local variable, using * either a symbol or a string name. If the specified variable does not exist, * returns nil. * * a = Thread.new { Thread.current["name"] = "A"; Thread.stop } * b = Thread.new { Thread.current[:name] = "B"; Thread.stop } * c = Thread.new { Thread.current["name"] = "C"; Thread.stop } * Thread.list.each {|x| puts "#{x.inspect}: #{x[:name]}" } * * produces: * * #: C * #: B * #: A * #: */ static VALUE rb_thread_aref(thread, id) VALUE thread, id; { return rb_thread_local_aref(thread, rb_to_id(id)); } VALUE rb_thread_local_aset(thread, id, val) VALUE thread; ID id; VALUE val; { rb_thread_t th = rb_thread_check(thread); if (ruby_safe_level >= 4 && th != curr_thread) { rb_raise(rb_eSecurityError, "Insecure: can't modify thread locals"); } if (OBJ_FROZEN(thread)) rb_error_frozen("thread locals"); if (!th->locals) { th->locals = st_init_numtable(); } if (NIL_P(val)) { st_delete(th->locals, (st_data_t*)&id, 0); return Qnil; } st_insert(th->locals, id, val); return val; } /* * call-seq: * thr[sym] = obj => obj * * Attribute Assignment---Sets or creates the value of a thread-local variable, * using either a symbol or a string. See also Thread#[]. */ static VALUE rb_thread_aset(thread, id, val) VALUE thread, id, val; { return rb_thread_local_aset(thread, rb_to_id(id), val); } /* * call-seq: * thr.key?(sym) => true or false * * Returns true if the given string (or symbol) exists as a * thread-local variable. * * me = Thread.current * me[:oliver] = "a" * me.key?(:oliver) #=> true * me.key?(:stanley) #=> false */ static VALUE rb_thread_key_p(thread, id) VALUE thread, id; { rb_thread_t th = rb_thread_check(thread); if (!th->locals) return Qfalse; if (st_lookup(th->locals, rb_to_id(id), 0)) return Qtrue; return Qfalse; } static int thread_keys_i(key, value, ary) ID key; VALUE value, ary; { rb_ary_push(ary, ID2SYM(key)); return ST_CONTINUE; } /* * call-seq: * thr.keys => array * * Returns an an array of the names of the thread-local variables (as Symbols). * * thr = Thread.new do * Thread.current[:cat] = 'meow' * Thread.current["dog"] = 'woof' * end * thr.join #=> # * thr.keys #=> [:dog, :cat] */ static VALUE rb_thread_keys(thread) VALUE thread; { rb_thread_t th = rb_thread_check(thread); VALUE ary = rb_ary_new(); if (th->locals) { st_foreach(th->locals, thread_keys_i, ary); } return ary; } /* * call-seq: * thr.inspect => string * * Dump the name, id, and status of _thr_ to a string. */ static VALUE rb_thread_inspect(thread) VALUE thread; { char *cname = rb_obj_classname(thread); rb_thread_t th = rb_thread_check(thread); const char *status = thread_status_name(th->status); VALUE str; size_t len = strlen(cname)+7+16+9+1; str = rb_str_new(0, len); /* 7:tags 16:addr 9:status 1:nul */ snprintf(RSTRING(str)->ptr, len, "#<%s:0x%lx %s>", cname, thread, status); RSTRING(str)->len = strlen(RSTRING(str)->ptr); OBJ_INFECT(str, thread); return str; } void rb_thread_atfork() { rb_thread_t th; if (rb_thread_alone()) return; FOREACH_THREAD(th) { if (th != curr_thread) { rb_thread_die(th); } } END_FOREACH(th); main_thread = curr_thread; curr_thread->next = curr_thread; curr_thread->prev = curr_thread; } static void cc_purge(cc) rb_thread_t cc; { /* free continuation's stack if it has just died */ if (NIL_P(cc->thread)) return; if (rb_thread_check(cc->thread)->status == THREAD_KILLED) { cc->thread = Qnil; rb_thread_die(cc); /* can't possibly activate this stack */ } } static void cc_mark(cc) rb_thread_t cc; { /* mark this continuation's stack only if its parent thread is still alive */ cc_purge(cc); thread_mark(cc); } static rb_thread_t rb_cont_check(data) VALUE data; { if (TYPE(data) != T_DATA || RDATA(data)->dmark != (RUBY_DATA_FUNC)cc_mark) { rb_raise(rb_eTypeError, "wrong argument type %s (expected Continuation)", rb_obj_classname(data)); } return (rb_thread_t)RDATA(data)->data; } /* * Document-class: Continuation * * Continuation objects are generated by * Kernel#callcc. They hold a return address and execution * context, allowing a nonlocal return to the end of the * callcc block from anywhere within a program. * Continuations are somewhat analogous to a structured version of C's * setjmp/longjmp (although they contain more state, so * you might consider them closer to threads). * * For instance: * * arr = [ "Freddie", "Herbie", "Ron", "Max", "Ringo" ] * callcc{|$cc|} * puts(message = arr.shift) * $cc.call unless message =~ /Max/ * * produces: * * Freddie * Herbie * Ron * Max * * This (somewhat contrived) example allows the inner loop to abandon * processing early: * * callcc {|cont| * for i in 0..4 * print "\n#{i}: " * for j in i*5...(i+1)*5 * cont.call() if j == 17 * printf "%3d", j * end * end * } * print "\n" * * produces: * * 0: 0 1 2 3 4 * 1: 5 6 7 8 9 * 2: 10 11 12 13 14 * 3: 15 16 */ VALUE rb_cCont; /* * call-seq: * callcc {|cont| block } => obj * * Generates a Continuation object, which it passes to the * associated block. Performing a cont.call will * cause the callcc to return (as will falling through the * end of the block). The value returned by the callcc is * the value of the block, or the value passed to * cont.call. See class Continuation * for more details. Also see Kernel::throw for * an alternative mechanism for unwinding a call stack. */ static VALUE rb_callcc(self) VALUE self; { volatile VALUE cont; rb_thread_t th; volatile rb_thread_t th_save; struct tag *tag; struct RVarmap *vars; THREAD_ALLOC(th); /* must finish th initialization before any possible gc. * brent@mbari.org */ th->thread = curr_thread->thread; th->thgroup = cont_protect; cont = Data_Wrap_Struct(rb_cCont, cc_mark, thread_free, th); scope_dup(ruby_scope); for (tag=prot_tag; tag; tag=tag->prev) { scope_dup(tag->scope); } for (vars = ruby_dyna_vars; vars; vars = vars->next) { if (FL_TEST(vars, DVAR_DONT_RECYCLE)) break; FL_SET(vars, DVAR_DONT_RECYCLE); } th_save = th; if (THREAD_SAVE_CONTEXT(th)) { return th_save->result; } else { return rb_yield(cont); } } /* * call-seq: * cont.call(args, ...) * cont[args, ...] * * Invokes the continuation. The program continues from the end of the * callcc block. If no arguments are given, the original * callcc returns nil. If one argument is * given, callcc returns it. Otherwise, an array * containing args is returned. * * callcc {|cont| cont.call } #=> nil * callcc {|cont| cont.call 1 } #=> 1 * callcc {|cont| cont.call 1, 2, 3 } #=> [1, 2, 3] */ static VALUE rb_cont_call(argc, argv, cont) int argc; VALUE *argv; VALUE cont; { rb_thread_t th = rb_cont_check(cont); if (th->thread != curr_thread->thread) { rb_raise(rb_eRuntimeError, "continuation called across threads"); } if (th->thgroup != cont_protect) { rb_raise(rb_eRuntimeError, "continuation called across trap"); } switch (argc) { case 0: th->result = Qnil; break; case 1: th->result = argv[0]; break; default: th->result = rb_ary_new4(argc, argv); break; } rb_thread_restore_context(th, RESTORE_NORMAL); return Qnil; } struct thgroup { int enclosed; VALUE group; }; /* * Document-class: ThreadGroup * * ThreadGroup provides a means of keeping track of a number of * threads as a group. A Thread can belong to only one * ThreadGroup at a time; adding a thread to a new group will * remove it from any previous group. * * Newly created threads belong to the same group as the thread from which they * were created. */ static VALUE thgroup_s_alloc _((VALUE)); static VALUE thgroup_s_alloc(klass) VALUE klass; { VALUE group; struct thgroup *data; group = Data_Make_Struct(klass, struct thgroup, 0, free, data); data->enclosed = 0; data->group = group; return group; } /* * call-seq: * thgrp.list => array * * Returns an array of all existing Thread objects that belong to * this group. * * ThreadGroup::Default.list #=> [#] */ static VALUE thgroup_list(group) VALUE group; { struct thgroup *data; rb_thread_t th; VALUE ary; Data_Get_Struct(group, struct thgroup, data); ary = rb_ary_new(); FOREACH_THREAD(th) { if (th->thgroup == data->group) { rb_ary_push(ary, th->thread); } } END_FOREACH(th); return ary; } /* * call-seq: * thgrp.enclose => thgrp * * Prevents threads from being added to or removed from the receiving * ThreadGroup. New threads can still be started in an enclosed * ThreadGroup. * * ThreadGroup::Default.enclose #=> # * thr = Thread::new { Thread.stop } #=> # * tg = ThreadGroup::new #=> # * tg.add thr * * produces: * * ThreadError: can't move from the enclosed thread group */ static VALUE thgroup_enclose(group) VALUE group; { struct thgroup *data; Data_Get_Struct(group, struct thgroup, data); data->enclosed = 1; return group; } /* * call-seq: * thgrp.enclosed? => true or false * * Returns true if thgrp is enclosed. See also * ThreadGroup#enclose. */ static VALUE thgroup_enclosed_p(group) VALUE group; { struct thgroup *data; Data_Get_Struct(group, struct thgroup, data); if (data->enclosed) return Qtrue; return Qfalse; } /* * call-seq: * thgrp.add(thread) => thgrp * * Adds the given thread to this group, removing it from any other * group to which it may have previously belonged. * * puts "Initial group is #{ThreadGroup::Default.list}" * tg = ThreadGroup.new * t1 = Thread.new { sleep } * t2 = Thread.new { sleep } * puts "t1 is #{t1}" * puts "t2 is #{t2}" * tg.add(t1) * puts "Initial group now #{ThreadGroup::Default.list}" * puts "tg group now #{tg.list}" * * produces: * * Initial group is # * t1 is # * t2 is # * Initial group now ## * tg group now # */ static VALUE thgroup_add(group, thread) VALUE group, thread; { rb_thread_t th; struct thgroup *data; rb_secure(4); th = rb_thread_check(thread); if (OBJ_FROZEN(group)) { rb_raise(rb_eThreadError, "can't move to the frozen thread group"); } Data_Get_Struct(group, struct thgroup, data); if (data->enclosed) { rb_raise(rb_eThreadError, "can't move to the enclosed thread group"); } if (!th->thgroup) { return Qnil; } if (OBJ_FROZEN(th->thgroup)) { rb_raise(rb_eThreadError, "can't move from the frozen thread group"); } Data_Get_Struct(th->thgroup, struct thgroup, data); if (data->enclosed) { rb_raise(rb_eThreadError, "can't move from the enclosed thread group"); } th->thgroup = group; return group; } /* * +Thread+ encapsulates the behavior of a thread of * execution, including the main thread of the Ruby script. * * In the descriptions of the methods in this class, the parameter _sym_ * refers to a symbol, which is either a quoted string or a * +Symbol+ (such as :name). */ void Init_Thread() { VALUE cThGroup; rb_eThreadError = rb_define_class("ThreadError", rb_eStandardError); rb_cThread = rb_define_class("Thread", rb_cObject); rb_undef_alloc_func(rb_cThread); rb_define_singleton_method(rb_cThread, "new", rb_thread_s_new, -1); rb_define_method(rb_cThread, "initialize", rb_thread_initialize, -2); rb_define_singleton_method(rb_cThread, "start", rb_thread_start, -2); rb_define_singleton_method(rb_cThread, "fork", rb_thread_start, -2); rb_define_singleton_method(rb_cThread, "stop", rb_thread_stop, 0); rb_define_singleton_method(rb_cThread, "kill", rb_thread_s_kill, 1); rb_define_singleton_method(rb_cThread, "exit", rb_thread_exit, 0); rb_define_singleton_method(rb_cThread, "pass", rb_thread_pass, 0); rb_define_singleton_method(rb_cThread, "current", rb_thread_current, 0); rb_define_singleton_method(rb_cThread, "main", rb_thread_main, 0); rb_define_singleton_method(rb_cThread, "list", rb_thread_list, 0); rb_define_singleton_method(rb_cThread, "critical", rb_thread_critical_get, 0); rb_define_singleton_method(rb_cThread, "critical=", rb_thread_critical_set, 1); rb_define_singleton_method(rb_cThread, "abort_on_exception", rb_thread_s_abort_exc, 0); rb_define_singleton_method(rb_cThread, "abort_on_exception=", rb_thread_s_abort_exc_set, 1); rb_define_method(rb_cThread, "run", rb_thread_run, 0); rb_define_method(rb_cThread, "wakeup", rb_thread_wakeup, 0); rb_define_method(rb_cThread, "kill", rb_thread_kill, 0); rb_define_method(rb_cThread, "terminate", rb_thread_kill, 0); rb_define_method(rb_cThread, "exit", rb_thread_kill, 0); rb_define_method(rb_cThread, "kill!", rb_thread_kill_bang, 0); rb_define_method(rb_cThread, "terminate!", rb_thread_kill_bang, 0); rb_define_method(rb_cThread, "exit!", rb_thread_kill_bang, 0); rb_define_method(rb_cThread, "value", rb_thread_value, 0); rb_define_method(rb_cThread, "status", rb_thread_status_name, 0); rb_define_method(rb_cThread, "join", rb_thread_join_m, -1); rb_define_method(rb_cThread, "alive?", rb_thread_alive_p, 0); rb_define_method(rb_cThread, "stop?", rb_thread_stop_p, 0); rb_define_method(rb_cThread, "raise", rb_thread_raise_m, -1); rb_define_method(rb_cThread, "abort_on_exception", rb_thread_abort_exc, 0); rb_define_method(rb_cThread, "abort_on_exception=", rb_thread_abort_exc_set, 1); rb_define_method(rb_cThread, "priority", rb_thread_priority, 0); rb_define_method(rb_cThread, "priority=", rb_thread_priority_set, 1); rb_define_method(rb_cThread, "safe_level", rb_thread_safe_level, 0); rb_define_method(rb_cThread, "group", rb_thread_group, 0); rb_define_method(rb_cThread, "[]", rb_thread_aref, 1); rb_define_method(rb_cThread, "[]=", rb_thread_aset, 2); rb_define_method(rb_cThread, "key?", rb_thread_key_p, 1); rb_define_method(rb_cThread, "keys", rb_thread_keys, 0); rb_define_method(rb_cThread, "inspect", rb_thread_inspect, 0); rb_cCont = rb_define_class("Continuation", rb_cObject); rb_undef_alloc_func(rb_cCont); rb_undef_method(CLASS_OF(rb_cCont), "new"); rb_define_method(rb_cCont, "call", rb_cont_call, -1); rb_define_method(rb_cCont, "[]", rb_cont_call, -1); rb_define_global_function("callcc", rb_callcc, 0); rb_global_variable(&cont_protect); cThGroup = rb_define_class("ThreadGroup", rb_cObject); rb_define_alloc_func(cThGroup, thgroup_s_alloc); rb_define_method(cThGroup, "list", thgroup_list, 0); rb_define_method(cThGroup, "enclose", thgroup_enclose, 0); rb_define_method(cThGroup, "enclosed?", thgroup_enclosed_p, 0); rb_define_method(cThGroup, "add", thgroup_add, 1); rb_global_variable(&thgroup_default); thgroup_default = rb_obj_alloc(cThGroup); rb_define_const(cThGroup, "Default", thgroup_default); /* allocate main thread */ main_thread = rb_thread_alloc(rb_cThread); curr_thread = main_thread->prev = main_thread->next = main_thread; } /* * call-seq: * catch(symbol) {| | block } > obj * * +catch+ executes its block. If a +throw+ is * executed, Ruby searches up its stack for a +catch+ block * with a tag corresponding to the +throw+'s * _symbol_. If found, that block is terminated, and * +catch+ returns the value given to +throw+. If * +throw+ is not called, the block terminates normally, and * the value of +catch+ is the value of the last expression * evaluated. +catch+ expressions may be nested, and the * +throw+ call need not be in lexical scope. * * def routine(n) * puts n * throw :done if n <= 0 * routine(n-1) * end * * * catch(:done) { routine(3) } * * produces: * * 3 * 2 * 1 * 0 */ static VALUE rb_f_catch(dmy, tag) VALUE dmy, tag; { int state; VALUE val = Qnil; /* OK */ tag = ID2SYM(rb_to_id(tag)); PUSH_TAG(tag); if ((state = EXEC_TAG()) == 0) { val = rb_yield_0(tag, 0, 0, 0, Qfalse); } else if (state == TAG_THROW && tag == prot_tag->dst) { val = prot_tag->retval; state = 0; } POP_TAG(); if (state) JUMP_TAG(state); return val; } static VALUE catch_i(tag) VALUE tag; { return rb_funcall(Qnil, rb_intern("catch"), 1, tag); } VALUE rb_catch(tag, func, data) const char *tag; VALUE (*func)(); VALUE data; { return rb_iterate((VALUE(*)_((VALUE)))catch_i, ID2SYM(rb_intern(tag)), func, data); } /* * call-seq: * throw(symbol [, obj]) * * Transfers control to the end of the active +catch+ block * waiting for _symbol_. Raises +NameError+ if there * is no +catch+ block for the symbol. The optional second * parameter supplies a return value for the +catch+ block, * which otherwise defaults to +nil+. For examples, see * Kernel::catch. */ static VALUE rb_f_throw(argc, argv) int argc; VALUE *argv; { VALUE tag, value; struct tag *tt = prot_tag; rb_scan_args(argc, argv, "11", &tag, &value); tag = ID2SYM(rb_to_id(tag)); while (tt) { if (tt->tag == tag) { tt->dst = tag; tt->retval = value; break; } if (tt->tag == PROT_THREAD) { rb_raise(rb_eThreadError, "uncaught throw `%s' in thread 0x%lx", rb_id2name(SYM2ID(tag)), curr_thread); } tt = tt->prev; } if (!tt) { rb_name_error(SYM2ID(tag), "uncaught throw `%s'", rb_id2name(SYM2ID(tag))); } rb_trap_restore_mask(); JUMP_TAG(TAG_THROW); #ifndef __GNUC__ return Qnil; /* not reached */ #endif } void rb_throw(tag, val) const char *tag; VALUE val; { VALUE argv[2]; argv[0] = ID2SYM(rb_intern(tag)); argv[1] = val; rb_f_throw(2, argv); } /********************************************************************** file.c - $Author: wyhaines $ $Date: 2009-08-25 20:37:21 +0200 (Tue, 25 Aug 2009) $ created at: Mon Nov 15 12:24:34 JST 1993 Copyright (C) 1993-2003 Yukihiro Matsumoto Copyright (C) 2000 Network Applied Communication Laboratory, Inc. Copyright (C) 2000 Information-technology Promotion Agency, Japan **********************************************************************/ #ifdef _WIN32 #include "missing/file.h" #endif #ifdef __CYGWIN__ #define OpenFile WINAPI_OpenFile #include #include #undef OpenFile #endif #include "ruby.h" #include "rubyio.h" #include "rubysig.h" #include "util.h" #include "dln.h" #include #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_SYS_FILE_H # include #else int flock _((int, int)); #endif #ifdef HAVE_SYS_PARAM_H # include #endif #ifndef MAXPATHLEN # define MAXPATHLEN 1024 #endif #include VALUE rb_time_new _((time_t, time_t)); #ifdef HAVE_UTIME_H #include #elif defined HAVE_SYS_UTIME_H #include #endif #ifdef HAVE_PWD_H #include #endif #ifndef HAVE_STRING_H char *strrchr _((const char*,const char)); #endif #include #include #ifdef HAVE_SYS_MKDEV_H #include #endif #if defined(HAVE_FCNTL_H) #include #endif #if !defined HAVE_LSTAT && !defined lstat #define lstat stat #endif #if !HAVE_FSEEKO && !defined(fseeko) # define fseeko fseek #endif #ifdef __BEOS__ /* should not change ID if -1 */ static int be_chown(const char *path, uid_t owner, gid_t group) { if (owner == -1 || group == -1) { struct stat st; if (stat(path, &st) < 0) return -1; if (owner == -1) owner = st.st_uid; if (group == -1) group = st.st_gid; } return chown(path, owner, group); } #define chown be_chown static int be_fchown(int fd, uid_t owner, gid_t group) { if (owner == -1 || group == -1) { struct stat st; if (fstat(fd, &st) < 0) return -1; if (owner == -1) owner = st.st_uid; if (group == -1) group = st.st_gid; } return fchown(fd, owner, group); } #define fchown be_fchown #endif /* __BEOS__ */ VALUE rb_cFile; VALUE rb_mFileTest; VALUE rb_cStat; static long apply2files _((void (*)(const char *, void *), VALUE, void *)); static long apply2files(func, vargs, arg) void (*func)_((const char *, void *)); VALUE vargs; void *arg; { long i; VALUE path; struct RArray *args = RARRAY(vargs); for (i=0; ilen; i++) { path = args->ptr[i]; SafeStringValue(path); (*func)(StringValueCStr(path), arg); } return args->len; } /* * call-seq: * file.path -> filename * * Returns the pathname used to create file as a string. Does * not normalize the name. * * File.new("testfile").path #=> "testfile" * File.new("/tmp/../tmp/xxx", "w").path #=> "/tmp/../tmp/xxx" * */ static VALUE rb_file_path(obj) VALUE obj; { OpenFile *fptr; fptr = RFILE(rb_io_taint_check(obj))->fptr; rb_io_check_initialized(fptr); if (!fptr->path) return Qnil; return rb_tainted_str_new2(fptr->path); } static VALUE stat_new_0(klass, st) VALUE klass; struct stat *st; { struct stat *nst = 0; if (st) { nst = ALLOC(struct stat); *nst = *st; } return Data_Wrap_Struct(klass, NULL, free, nst); } static VALUE stat_new(st) struct stat *st; { return stat_new_0(rb_cStat, st); } static struct stat* get_stat(self) VALUE self; { struct stat* st; Data_Get_Struct(self, struct stat, st); if (!st) rb_raise(rb_eTypeError, "uninitialized File::Stat"); return st; } /* * call-seq: * stat <=> other_stat => -1, 0, 1 * * Compares File::Stat objects by comparing their * respective modification times. * * f1 = File.new("f1", "w") * sleep 1 * f2 = File.new("f2", "w") * f1.stat <=> f2.stat #=> -1 */ static VALUE rb_stat_cmp(self, other) VALUE self, other; { if (rb_obj_is_kind_of(other, rb_obj_class(self))) { time_t t1 = get_stat(self)->st_mtime; time_t t2 = get_stat(other)->st_mtime; if (t1 == t2) return INT2FIX(0); else if (t1 < t2) return INT2FIX(-1); else return INT2FIX(1); } return Qnil; } static VALUE rb_stat_dev _((VALUE)); static VALUE rb_stat_ino _((VALUE)); static VALUE rb_stat_mode _((VALUE)); static VALUE rb_stat_nlink _((VALUE)); static VALUE rb_stat_uid _((VALUE)); static VALUE rb_stat_gid _((VALUE)); static VALUE rb_stat_rdev _((VALUE)); static VALUE rb_stat_size _((VALUE)); static VALUE rb_stat_blksize _((VALUE)); static VALUE rb_stat_blocks _((VALUE)); static VALUE rb_stat_atime _((VALUE)); static VALUE rb_stat_mtime _((VALUE)); static VALUE rb_stat_ctime _((VALUE)); /* * call-seq: * stat.dev => fixnum * * Returns an integer representing the device on which stat * resides. * * File.stat("testfile").dev #=> 774 */ static VALUE rb_stat_dev(self) VALUE self; { return INT2NUM(get_stat(self)->st_dev); } /* * call-seq: * stat.dev_major => fixnum * * Returns the major part of File_Stat#dev or * nil. * * File.stat("/dev/fd1").dev_major #=> 2 * File.stat("/dev/tty").dev_major #=> 5 */ static VALUE rb_stat_dev_major(self) VALUE self; { #if defined(major) long dev = get_stat(self)->st_dev; return ULONG2NUM(major(dev)); #else return Qnil; #endif } /* * call-seq: * stat.dev_minor => fixnum * * Returns the minor part of File_Stat#dev or * nil. * * File.stat("/dev/fd1").dev_minor #=> 1 * File.stat("/dev/tty").dev_minor #=> 0 */ static VALUE rb_stat_dev_minor(self) VALUE self; { #if defined(minor) long dev = get_stat(self)->st_dev; return ULONG2NUM(minor(dev)); #else return Qnil; #endif } /* * call-seq: * stat.ino => fixnum * * Returns the inode number for stat. * * File.stat("testfile").ino #=> 1083669 * */ static VALUE rb_stat_ino(self) VALUE self; { #ifdef HUGE_ST_INO return ULL2NUM(get_stat(self)->st_ino); #else return ULONG2NUM(get_stat(self)->st_ino); #endif } /* * call-seq: * stat.mode => fixnum * * Returns an integer representing the permission bits of * stat. The meaning of the bits is platform dependent; on * Unix systems, see stat(2). * * File.chmod(0644, "testfile") #=> 1 * s = File.stat("testfile") * sprintf("%o", s.mode) #=> "100644" */ static VALUE rb_stat_mode(self) VALUE self; { #ifdef __BORLANDC__ return UINT2NUM((unsigned short)(get_stat(self)->st_mode)); #else return UINT2NUM(get_stat(self)->st_mode); #endif } /* * call-seq: * stat.nlink => fixnum * * Returns the number of hard links to stat. * * File.stat("testfile").nlink #=> 1 * File.link("testfile", "testfile.bak") #=> 0 * File.stat("testfile").nlink #=> 2 * */ static VALUE rb_stat_nlink(self) VALUE self; { return UINT2NUM(get_stat(self)->st_nlink); } /* * call-seq: * stat.uid => fixnum * * Returns the numeric user id of the owner of stat. * * File.stat("testfile").uid #=> 501 * */ static VALUE rb_stat_uid(self) VALUE self; { return UINT2NUM(get_stat(self)->st_uid); } /* * call-seq: * stat.gid => fixnum * * Returns the numeric group id of the owner of stat. * * File.stat("testfile").gid #=> 500 * */ static VALUE rb_stat_gid(self) VALUE self; { return UINT2NUM(get_stat(self)->st_gid); } /* * call-seq: * stat.rdev => fixnum or nil * * Returns an integer representing the device type on which * stat resides. Returns nil if the operating * system doesn't support this feature. * * File.stat("/dev/fd1").rdev #=> 513 * File.stat("/dev/tty").rdev #=> 1280 */ static VALUE rb_stat_rdev(self) VALUE self; { #ifdef HAVE_ST_RDEV return ULONG2NUM(get_stat(self)->st_rdev); #else return Qnil; #endif } /* * call-seq: * stat.rdev_major => fixnum * * Returns the major part of File_Stat#rdev or * nil. * * File.stat("/dev/fd1").rdev_major #=> 2 * File.stat("/dev/tty").rdev_major #=> 5 */ static VALUE rb_stat_rdev_major(self) VALUE self; { #if defined(HAVE_ST_RDEV) && defined(major) long rdev = get_stat(self)->st_rdev; return ULONG2NUM(major(rdev)); #else return Qnil; #endif } /* * call-seq: * stat.rdev_minor => fixnum * * Returns the minor part of File_Stat#rdev or * nil. * * File.stat("/dev/fd1").rdev_minor #=> 1 * File.stat("/dev/tty").rdev_minor #=> 0 */ static VALUE rb_stat_rdev_minor(self) VALUE self; { #if defined(HAVE_ST_RDEV) && defined(minor) long rdev = get_stat(self)->st_rdev; return ULONG2NUM(minor(rdev)); #else return Qnil; #endif } /* * call-seq: * stat.size => fixnum * * Returns the size of stat in bytes. * * File.stat("testfile").size #=> 66 */ static VALUE rb_stat_size(self) VALUE self; { return OFFT2NUM(get_stat(self)->st_size); } /* * call-seq: * stat.blksize => integer or nil * * Returns the native file system's block size. Will return nil * on platforms that don't support this information. * * File.stat("testfile").blksize #=> 4096 * */ static VALUE rb_stat_blksize(self) VALUE self; { #ifdef HAVE_ST_BLKSIZE return ULONG2NUM(get_stat(self)->st_blksize); #else return Qnil; #endif } /* * call-seq: * stat.blocks => integer or nil * * Returns the number of native file system blocks allocated for this * file, or nil if the operating system doesn't * support this feature. * * File.stat("testfile").blocks #=> 2 */ static VALUE rb_stat_blocks(self) VALUE self; { #ifdef HAVE_ST_BLOCKS return ULONG2NUM(get_stat(self)->st_blocks); #else return Qnil; #endif } /* * call-seq: * stat.atime => time * * Returns the last access time for this file as an object of class * Time. * * File.stat("testfile").atime #=> Wed Dec 31 18:00:00 CST 1969 * */ static VALUE rb_stat_atime(self) VALUE self; { return rb_time_new(get_stat(self)->st_atime, 0); } /* * call-seq: * stat.mtime -> aTime * * Returns the modification time of stat. * * File.stat("testfile").mtime #=> Wed Apr 09 08:53:14 CDT 2003 * */ static VALUE rb_stat_mtime(self) VALUE self; { return rb_time_new(get_stat(self)->st_mtime, 0); } /* * call-seq: * stat.ctime -> aTime * * Returns the change time for stat (that is, the time * directory information about the file was changed, not the file * itself). * * File.stat("testfile").ctime #=> Wed Apr 09 08:53:14 CDT 2003 * */ static VALUE rb_stat_ctime(self) VALUE self; { return rb_time_new(get_stat(self)->st_ctime, 0); } /* * call-seq: * stat.inspect => string * * Produce a nicely formatted description of stat. * * File.stat("/etc/passwd").inspect * #=> "#" */ static VALUE rb_stat_inspect(self) VALUE self; { VALUE str; int i; static const struct { const char *name; VALUE (*func)_((VALUE)); } member[] = { {"dev", rb_stat_dev}, {"ino", rb_stat_ino}, {"mode", rb_stat_mode}, {"nlink", rb_stat_nlink}, {"uid", rb_stat_uid}, {"gid", rb_stat_gid}, {"rdev", rb_stat_rdev}, {"size", rb_stat_size}, {"blksize", rb_stat_blksize}, {"blocks", rb_stat_blocks}, {"atime", rb_stat_atime}, {"mtime", rb_stat_mtime}, {"ctime", rb_stat_ctime}, }; str = rb_str_buf_new2("#<"); rb_str_buf_cat2(str, rb_obj_classname(self)); rb_str_buf_cat2(str, " "); for (i = 0; i < sizeof(member)/sizeof(member[0]); i++) { VALUE v; if (i > 0) { rb_str_buf_cat2(str, ", "); } rb_str_buf_cat2(str, member[i].name); rb_str_buf_cat2(str, "="); v = (*member[i].func)(self); if (i == 2) { /* mode */ char buf[32]; sprintf(buf, "0%lo", NUM2ULONG(v)); rb_str_buf_cat2(str, buf); } else if (i == 0 || i == 6) { /* dev/rdev */ char buf[32]; sprintf(buf, "0x%lx", NUM2ULONG(v)); rb_str_buf_cat2(str, buf); } else { rb_str_append(str, rb_inspect(v)); } } rb_str_buf_cat2(str, ">"); OBJ_INFECT(str, self); return str; } static int rb_stat(file, st) VALUE file; struct stat *st; { VALUE tmp; tmp = rb_check_convert_type(file, T_FILE, "IO", "to_io"); if (!NIL_P(tmp)) { OpenFile *fptr; rb_secure(2); GetOpenFile(tmp, fptr); return fstat(fileno(fptr->f), st); } SafeStringValue(file); return stat(StringValueCStr(file), st); } #ifdef _WIN32 static HANDLE w32_io_info(file, st) VALUE *file; BY_HANDLE_FILE_INFORMATION *st; { VALUE tmp; HANDLE f, ret = 0; tmp = rb_check_convert_type(*file, T_FILE, "IO", "to_io"); if (!NIL_P(tmp)) { OpenFile *fptr; GetOpenFile(tmp, fptr); f = (HANDLE)rb_w32_get_osfhandle(fileno(fptr->f)); if (f == (HANDLE)-1) return INVALID_HANDLE_VALUE; } else { SafeStringValue(*file); f = CreateFile(StringValueCStr(*file), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, rb_w32_iswin95() ? 0 : FILE_FLAG_BACKUP_SEMANTICS, NULL); if (f == INVALID_HANDLE_VALUE) return f; ret = f; } if (GetFileType(f) == FILE_TYPE_DISK) { ZeroMemory(st, sizeof(*st)); if (GetFileInformationByHandle(f, st)) return ret; } if (ret) CloseHandle(ret); return INVALID_HANDLE_VALUE; } #endif /* * call-seq: * File.stat(file_name) => stat * * Returns a File::Stat object for the named file (see * File::Stat). * * File.stat("testfile").mtime #=> Tue Apr 08 12:58:04 CDT 2003 * */ static VALUE rb_file_s_stat(klass, fname) VALUE klass, fname; { struct stat st; SafeStringValue(fname); if (rb_stat(fname, &st) < 0) { rb_sys_fail(StringValueCStr(fname)); } return stat_new(&st); } /* * call-seq: * ios.stat => stat * * Returns status information for ios as an object of type * File::Stat. * * f = File.new("testfile") * s = f.stat * "%o" % s.mode #=> "100644" * s.blksize #=> 4096 * s.atime #=> Wed Apr 09 08:53:54 CDT 2003 * */ static VALUE rb_io_stat(obj) VALUE obj; { OpenFile *fptr; struct stat st; GetOpenFile(obj, fptr); if (fstat(fileno(fptr->f), &st) == -1) { rb_sys_fail(fptr->path); } return stat_new(&st); } /* * call-seq: * File.lstat(file_name) => stat * * Same as File::stat, but does not follow the last symbolic * link. Instead, reports on the link itself. * * File.symlink("testfile", "link2test") #=> 0 * File.stat("testfile").size #=> 66 * File.lstat("link2test").size #=> 8 * File.stat("link2test").size #=> 66 * */ static VALUE rb_file_s_lstat(klass, fname) VALUE klass, fname; { #ifdef HAVE_LSTAT struct stat st; SafeStringValue(fname); if (lstat(StringValueCStr(fname), &st) == -1) { rb_sys_fail(RSTRING(fname)->ptr); } return stat_new(&st); #else return rb_file_s_stat(klass, fname); #endif } /* * call-seq: * file.lstat => stat * * Same as IO#stat, but does not follow the last symbolic * link. Instead, reports on the link itself. * * File.symlink("testfile", "link2test") #=> 0 * File.stat("testfile").size #=> 66 * f = File.new("link2test") * f.lstat.size #=> 8 * f.stat.size #=> 66 */ static VALUE rb_file_lstat(obj) VALUE obj; { #ifdef HAVE_LSTAT OpenFile *fptr; struct stat st; rb_secure(2); GetOpenFile(obj, fptr); if (!fptr->path) return Qnil; if (lstat(fptr->path, &st) == -1) { rb_sys_fail(fptr->path); } return stat_new(&st); #else return rb_io_stat(obj); #endif } #ifndef HAVE_GROUP_MEMBER static int group_member(gid) GETGROUPS_T gid; { #ifndef _WIN32 if (getgid() == gid || getegid() == gid) return Qtrue; # ifdef HAVE_GETGROUPS # ifndef NGROUPS # ifdef NGROUPS_MAX # define NGROUPS NGROUPS_MAX # else # define NGROUPS 32 # endif # endif { GETGROUPS_T gary[NGROUPS]; int anum; anum = getgroups(NGROUPS, gary); while (--anum >= 0) if (gary[anum] == gid) return Qtrue; } # endif #endif return Qfalse; } #endif #ifndef S_IXUGO # define S_IXUGO (S_IXUSR | S_IXGRP | S_IXOTH) #endif #if defined(S_IXGRP) && !defined(_WIN32) && !defined(__CYGWIN__) #define USE_GETEUID 1 #endif #ifndef HAVE_EACCESS int eaccess(path, mode) const char *path; int mode; { #ifdef USE_GETEUID struct stat st; int euid; if (stat(path, &st) < 0) return -1; euid = geteuid(); if (euid == 0) { /* Root can read or write any file. */ if (!(mode & X_OK)) return 0; /* Root can execute any file that has any one of the execute bits set. */ if (st.st_mode & S_IXUGO) return 0; return -1; } if (st.st_uid == euid) /* owner */ mode <<= 6; else if (group_member(st.st_gid)) mode <<= 3; if ((st.st_mode & mode) == mode) return 0; return -1; #else # if _MSC_VER >= 1400 mode &= 6; # endif return access(path, mode); #endif } #endif /* * Document-class: FileTest * * FileTest implements file test operations similar to * those used in File::Stat. It exists as a standalone * module, and its methods are also insinuated into the File * class. (Note that this is not done by inclusion: the interpreter cheats). * */ /* * call-seq: * File.directory?(file_name) => true or false * * Returns true if the named file is a directory, * false otherwise. * * File.directory?(".") */ static VALUE test_d(obj, fname) VALUE obj, fname; { #ifndef S_ISDIR # define S_ISDIR(m) ((m & S_IFMT) == S_IFDIR) #endif struct stat st; if (rb_stat(fname, &st) < 0) return Qfalse; if (S_ISDIR(st.st_mode)) return Qtrue; return Qfalse; } /* * call-seq: * File.pipe?(file_name) => true or false * * Returns true if the named file is a pipe. */ static VALUE test_p(obj, fname) VALUE obj, fname; { #ifdef S_IFIFO # ifndef S_ISFIFO # define S_ISFIFO(m) ((m & S_IFMT) == S_IFIFO) # endif struct stat st; if (rb_stat(fname, &st) < 0) return Qfalse; if (S_ISFIFO(st.st_mode)) return Qtrue; #endif return Qfalse; } /* * call-seq: * File.symlink?(file_name) => true or false * * Returns true if the named file is a symbolic link. */ static VALUE test_l(obj, fname) VALUE obj, fname; { #ifndef S_ISLNK # ifdef _S_ISLNK # define S_ISLNK(m) _S_ISLNK(m) # elif defined __BORLANDC__ # ifdef _S_IFLNK # define S_ISLNK(m) (((unsigned short)(m) & S_IFMT) == _S_IFLNK) # else # ifdef S_IFLNK # define S_ISLNK(m) (((unsigned short)(m) & S_IFMT) == S_IFLNK) # endif # endif # else # ifdef _S_IFLNK # define S_ISLNK(m) ((m & S_IFMT) == _S_IFLNK) # else # ifdef S_IFLNK # define S_ISLNK(m) ((m & S_IFMT) == S_IFLNK) # endif # endif # endif #endif #ifdef S_ISLNK struct stat st; SafeStringValue(fname); if (lstat(StringValueCStr(fname), &st) < 0) return Qfalse; if (S_ISLNK(st.st_mode)) return Qtrue; #endif return Qfalse; } /* * call-seq: * File.socket?(file_name) => true or false * * Returns true if the named file is a socket. */ static VALUE test_S(obj, fname) VALUE obj, fname; { #ifndef S_ISSOCK # ifdef _S_ISSOCK # define S_ISSOCK(m) _S_ISSOCK(m) # elif defined __BORLANDC__ # ifdef _S_IFSOCK # define S_ISSOCK(m) (((unsigned short)(m) & S_IFMT) == _S_IFSOCK) # else # ifdef S_IFSOCK # define S_ISSOCK(m) (((unsigned short)(m) & S_IFMT) == S_IFSOCK) # endif # endif # else # ifdef _S_IFSOCK # define S_ISSOCK(m) ((m & S_IFMT) == _S_IFSOCK) # else # ifdef S_IFSOCK # define S_ISSOCK(m) ((m & S_IFMT) == S_IFSOCK) # endif # endif # endif #endif #ifdef S_ISSOCK struct stat st; if (rb_stat(fname, &st) < 0) return Qfalse; if (S_ISSOCK(st.st_mode)) return Qtrue; #endif return Qfalse; } /* * call-seq: * File.blockdev?(file_name) => true or false * * Returns true if the named file is a block device. */ static VALUE test_b(obj, fname) VALUE obj, fname; { #ifndef S_ISBLK # ifdef S_IFBLK # define S_ISBLK(m) ((m & S_IFMT) == S_IFBLK) # else # define S_ISBLK(m) (0) /* anytime false */ # endif #endif #ifdef S_ISBLK struct stat st; if (rb_stat(fname, &st) < 0) return Qfalse; if (S_ISBLK(st.st_mode)) return Qtrue; #endif return Qfalse; } /* * call-seq: * File.chardev?(file_name) => true or false * * Returns true if the named file is a character device. */ static VALUE test_c(obj, fname) VALUE obj, fname; { #ifndef S_ISCHR # define S_ISCHR(m) ((m & S_IFMT) == S_IFCHR) #endif struct stat st; if (rb_stat(fname, &st) < 0) return Qfalse; if (S_ISCHR(st.st_mode)) return Qtrue; return Qfalse; } /* * call-seq: * File.exist?(file_name) => true or false * File.exists?(file_name) => true or false (obsolete) * * Return true if the named file exists. */ static VALUE test_e(obj, fname) VALUE obj, fname; { struct stat st; if (rb_stat(fname, &st) < 0) return Qfalse; return Qtrue; } /* * call-seq: * File.readable?(file_name) => true or false * * Returns true if the named file is readable by the effective * user id of this process. */ static VALUE test_r(obj, fname) VALUE obj, fname; { SafeStringValue(fname); if (eaccess(StringValueCStr(fname), R_OK) < 0) return Qfalse; return Qtrue; } /* * call-seq: * File.readable_real?(file_name) => true or false * * Returns true if the named file is readable by the real * user id of this process. */ static VALUE test_R(obj, fname) VALUE obj, fname; { SafeStringValue(fname); if (access(StringValueCStr(fname), R_OK) < 0) return Qfalse; return Qtrue; } /* * call-seq: * File.writable?(file_name) => true or false * * Returns true if the named file is writable by the effective * user id of this process. */ static VALUE test_w(obj, fname) VALUE obj, fname; { SafeStringValue(fname); if (eaccess(StringValueCStr(fname), W_OK) < 0) return Qfalse; return Qtrue; } /* * call-seq: * File.writable_real?(file_name) => true or false * * Returns true if the named file is writable by the real * user id of this process. */ static VALUE test_W(obj, fname) VALUE obj, fname; { SafeStringValue(fname); if (access(StringValueCStr(fname), W_OK) < 0) return Qfalse; return Qtrue; } /* * call-seq: * File.executable?(file_name) => true or false * * Returns true if the named file is executable by the effective * user id of this process. */ static VALUE test_x(obj, fname) VALUE obj, fname; { SafeStringValue(fname); if (eaccess(StringValueCStr(fname), X_OK) < 0) return Qfalse; return Qtrue; } /* * call-seq: * File.executable_real?(file_name) => true or false * * Returns true if the named file is executable by the real * user id of this process. */ static VALUE test_X(obj, fname) VALUE obj, fname; { SafeStringValue(fname); if (access(StringValueCStr(fname), X_OK) < 0) return Qfalse; return Qtrue; } #ifndef S_ISREG # define S_ISREG(m) ((m & S_IFMT) == S_IFREG) #endif /* * call-seq: * File.file?(file_name) => true or false * * Returns true if the named file exists and is a * regular file. */ static VALUE test_f(obj, fname) VALUE obj, fname; { struct stat st; if (rb_stat(fname, &st) < 0) return Qfalse; if (S_ISREG(st.st_mode)) return Qtrue; return Qfalse; } /* * call-seq: * File.zero?(file_name) => true or false * * Returns true if the named file exists and has * a zero size. */ static VALUE test_z(obj, fname) VALUE obj, fname; { struct stat st; if (rb_stat(fname, &st) < 0) return Qfalse; if (st.st_size == 0) return Qtrue; return Qfalse; } /* * call-seq: * File.size?(file_name) => Integer or nil * * Returns +nil+ if +file_name+ doesn't exist or has zero size, the size of the * file otherwise. */ static VALUE test_s(obj, fname) VALUE obj, fname; { struct stat st; if (rb_stat(fname, &st) < 0) return Qnil; if (st.st_size == 0) return Qnil; return OFFT2NUM(st.st_size); } /* * call-seq: * File.owned?(file_name) => true or false * * Returns true if the named file exists and the * effective used id of the calling process is the owner of * the file. */ static VALUE test_owned(obj, fname) VALUE obj, fname; { struct stat st; if (rb_stat(fname, &st) < 0) return Qfalse; if (st.st_uid == geteuid()) return Qtrue; return Qfalse; } static VALUE test_rowned(obj, fname) VALUE obj, fname; { struct stat st; if (rb_stat(fname, &st) < 0) return Qfalse; if (st.st_uid == getuid()) return Qtrue; return Qfalse; } /* * call-seq: * File.grpowned?(file_name) => true or false * * Returns true if the named file exists and the * effective group id of the calling process is the owner of * the file. Returns false on Windows. */ static VALUE test_grpowned(obj, fname) VALUE obj, fname; { #ifndef _WIN32 struct stat st; if (rb_stat(fname, &st) < 0) return Qfalse; if (group_member(st.st_gid)) return Qtrue; #endif return Qfalse; } #if defined(S_ISUID) || defined(S_ISGID) || defined(S_ISVTX) static VALUE check3rdbyte(fname, mode) VALUE fname; int mode; { struct stat st; SafeStringValue(fname); if (stat(StringValueCStr(fname), &st) < 0) return Qfalse; if (st.st_mode & mode) return Qtrue; return Qfalse; } #endif /* * call-seq: * File.setuid?(file_name) => true or false * * Returns true if the named file has the setuid bit set. */ static VALUE test_suid(obj, fname) VALUE obj, fname; { #ifdef S_ISUID return check3rdbyte(fname, S_ISUID); #else return Qfalse; #endif } /* * call-seq: * File.setgid?(file_name) => true or false * * Returns true if the named file has the setgid bit set. */ static VALUE test_sgid(obj, fname) VALUE obj, fname; { #ifdef S_ISGID return check3rdbyte(fname, S_ISGID); #else return Qfalse; #endif } /* * call-seq: * File.sticky?(file_name) => true or false * * Returns true if the named file has the sticky bit set. */ static VALUE test_sticky(obj, fname) VALUE obj, fname; { #ifdef S_ISVTX return check3rdbyte(fname, S_ISVTX); #else return Qnil; #endif } /* * call-seq: * File.identical?(file_1, file_2) => true or false * * Returns true if the named files are identical. * * open("a", "w") {} * p File.identical?("a", "a") #=> true * p File.identical?("a", "./a") #=> true * File.link("a", "b") * p File.identical?("a", "b") #=> true * File.symlink("a", "c") * p File.identical?("a", "c") #=> true * open("d", "w") {} * p File.identical?("a", "d") #=> false */ static VALUE test_identical(obj, fname1, fname2) VALUE obj, fname1, fname2; { #ifndef DOSISH struct stat st1, st2; if (rb_stat(fname1, &st1) < 0) return Qfalse; if (rb_stat(fname2, &st2) < 0) return Qfalse; if (st1.st_dev != st2.st_dev) return Qfalse; if (st1.st_ino != st2.st_ino) return Qfalse; #else #ifdef _WIN32 BY_HANDLE_FILE_INFORMATION st1, st2; HANDLE f1 = 0, f2 = 0; #endif rb_secure(2); #ifdef _WIN32 f1 = w32_io_info(&fname1, &st1); if (f1 == INVALID_HANDLE_VALUE) return Qfalse; f2 = w32_io_info(&fname2, &st2); if (f1) CloseHandle(f1); if (f2 == INVALID_HANDLE_VALUE) return Qfalse; if (f2) CloseHandle(f2); if (st1.dwVolumeSerialNumber == st2.dwVolumeSerialNumber && st1.nFileIndexHigh == st2.nFileIndexHigh && st1.nFileIndexLow == st2.nFileIndexLow) return Qtrue; if (!f1 || !f2) return Qfalse; if (rb_w32_iswin95()) return Qfalse; #else SafeStringValue(fname1); fname1 = rb_str_new4(fname1); SafeStringValue(fname2); if (access(RSTRING(fname1)->ptr, 0)) return Qfalse; if (access(RSTRING(fname2)->ptr, 0)) return Qfalse; #endif fname1 = rb_file_expand_path(fname1, Qnil); fname2 = rb_file_expand_path(fname2, Qnil); if (RSTRING(fname1)->len != RSTRING(fname2)->len) return Qfalse; if (rb_memcicmp(RSTRING(fname1)->ptr, RSTRING(fname2)->ptr, RSTRING(fname1)->len)) return Qfalse; #endif return Qtrue; } /* * call-seq: * File.size(file_name) => integer * * Returns the size of file_name. */ static VALUE rb_file_s_size(klass, fname) VALUE klass, fname; { struct stat st; if (rb_stat(fname, &st) < 0) rb_sys_fail(StringValueCStr(fname)); return OFFT2NUM(st.st_size); } static VALUE rb_file_ftype(st) struct stat *st; { char *t; if (S_ISREG(st->st_mode)) { t = "file"; } else if (S_ISDIR(st->st_mode)) { t = "directory"; } else if (S_ISCHR(st->st_mode)) { t = "characterSpecial"; } #ifdef S_ISBLK else if (S_ISBLK(st->st_mode)) { t = "blockSpecial"; } #endif #ifdef S_ISFIFO else if (S_ISFIFO(st->st_mode)) { t = "fifo"; } #endif #ifdef S_ISLNK else if (S_ISLNK(st->st_mode)) { t = "link"; } #endif #ifdef S_ISSOCK else if (S_ISSOCK(st->st_mode)) { t = "socket"; } #endif else { t = "unknown"; } return rb_str_new2(t); } /* * call-seq: * File.ftype(file_name) => string * * Identifies the type of the named file; the return string is one of * ``file'', ``directory'', * ``characterSpecial'', ``blockSpecial'', * ``fifo'', ``link'', * ``socket'', or ``unknown''. * * File.ftype("testfile") #=> "file" * File.ftype("/dev/tty") #=> "characterSpecial" * File.ftype("/tmp/.X11-unix/X0") #=> "socket" */ static VALUE rb_file_s_ftype(klass, fname) VALUE klass, fname; { struct stat st; SafeStringValue(fname); if (lstat(StringValueCStr(fname), &st) == -1) { rb_sys_fail(RSTRING(fname)->ptr); } return rb_file_ftype(&st); } /* * call-seq: * File.atime(file_name) => time * * Returns the last access time for the named file as a Time object). * * File.atime("testfile") #=> Wed Apr 09 08:51:48 CDT 2003 * */ static VALUE rb_file_s_atime(klass, fname) VALUE klass, fname; { struct stat st; if (rb_stat(fname, &st) < 0) rb_sys_fail(StringValueCStr(fname)); return rb_time_new(st.st_atime, 0); } /* * call-seq: * file.atime => time * * Returns the last access time (a Time object) * for file, or epoch if file has not been accessed. * * File.new("testfile").atime #=> Wed Dec 31 18:00:00 CST 1969 * */ static VALUE rb_file_atime(obj) VALUE obj; { OpenFile *fptr; struct stat st; GetOpenFile(obj, fptr); if (fstat(fileno(fptr->f), &st) == -1) { rb_sys_fail(fptr->path); } return rb_time_new(st.st_atime, 0); } /* * call-seq: * File.mtime(file_name) => time * * Returns the modification time for the named file as a Time object. * * File.mtime("testfile") #=> Tue Apr 08 12:58:04 CDT 2003 * */ static VALUE rb_file_s_mtime(klass, fname) VALUE klass, fname; { struct stat st; if (rb_stat(fname, &st) < 0) rb_sys_fail(RSTRING(fname)->ptr); return rb_time_new(st.st_mtime, 0); } /* * call-seq: * file.mtime -> time * * Returns the modification time for file. * * File.new("testfile").mtime #=> Wed Apr 09 08:53:14 CDT 2003 * */ static VALUE rb_file_mtime(obj) VALUE obj; { OpenFile *fptr; struct stat st; GetOpenFile(obj, fptr); if (fstat(fileno(fptr->f), &st) == -1) { rb_sys_fail(fptr->path); } return rb_time_new(st.st_mtime, 0); } /* * call-seq: * File.ctime(file_name) => time * * Returns the change time for the named file (the time at which * directory information about the file was changed, not the file * itself). * * File.ctime("testfile") #=> Wed Apr 09 08:53:13 CDT 2003 * */ static VALUE rb_file_s_ctime(klass, fname) VALUE klass, fname; { struct stat st; if (rb_stat(fname, &st) < 0) rb_sys_fail(RSTRING(fname)->ptr); return rb_time_new(st.st_ctime, 0); } /* * call-seq: * file.ctime -> time * * Returns the change time for file (that is, the time directory * information about the file was changed, not the file itself). * * File.new("testfile").ctime #=> Wed Apr 09 08:53:14 CDT 2003 * */ static VALUE rb_file_ctime(obj) VALUE obj; { OpenFile *fptr; struct stat st; GetOpenFile(obj, fptr); if (fstat(fileno(fptr->f), &st) == -1) { rb_sys_fail(fptr->path); } return rb_time_new(st.st_ctime, 0); } static void chmod_internal _((const char *, void *)); static void chmod_internal(path, mode) const char *path; void *mode; { if (chmod(path, *(int *)mode) < 0) rb_sys_fail(path); } /* * call-seq: * File.chmod(mode_int, file_name, ... ) -> integer * * Changes permission bits on the named file(s) to the bit pattern * represented by mode_int. Actual effects are operating system * dependent (see the beginning of this section). On Unix systems, see * chmod(2) for details. Returns the number of files * processed. * * File.chmod(0644, "testfile", "out") #=> 2 */ static VALUE rb_file_s_chmod(argc, argv) int argc; VALUE *argv; { VALUE vmode; VALUE rest; int mode; long n; rb_secure(2); rb_scan_args(argc, argv, "1*", &vmode, &rest); mode = NUM2INT(vmode); n = apply2files(chmod_internal, rest, &mode); return LONG2FIX(n); } /* * call-seq: * file.chmod(mode_int) => 0 * * Changes permission bits on file to the bit pattern * represented by mode_int. Actual effects are platform * dependent; on Unix systems, see chmod(2) for details. * Follows symbolic links. Also see File#lchmod. * * f = File.new("out", "w"); * f.chmod(0644) #=> 0 */ static VALUE rb_file_chmod(obj, vmode) VALUE obj, vmode; { OpenFile *fptr; int mode; rb_secure(2); mode = NUM2INT(vmode); GetOpenFile(obj, fptr); #ifdef HAVE_FCHMOD if (fchmod(fileno(fptr->f), mode) == -1) rb_sys_fail(fptr->path); #else if (!fptr->path) return Qnil; if (chmod(fptr->path, mode) == -1) rb_sys_fail(fptr->path); #endif return INT2FIX(0); } #if defined(HAVE_LCHMOD) static void lchmod_internal _((const char *, void *)); static void lchmod_internal(path, mode) const char *path; void *mode; { if (lchmod(path, (int)mode) < 0) rb_sys_fail(path); } /* * call-seq: * File.lchmod(mode_int, file_name, ...) => integer * * Equivalent to File::chmod, but does not follow symbolic * links (so it will change the permissions associated with the link, * not the file referenced by the link). Often not available. * */ static VALUE rb_file_s_lchmod(argc, argv) int argc; VALUE *argv; { VALUE vmode; VALUE rest; long mode, n; rb_secure(2); rb_scan_args(argc, argv, "1*", &vmode, &rest); mode = NUM2INT(vmode); n = apply2files(lchmod_internal, rest, (void *)(long)mode); return LONG2FIX(n); } #else static VALUE rb_file_s_lchmod(argc, argv) int argc; VALUE *argv; { rb_notimplement(); return Qnil; /* not reached */ } #endif struct chown_args { int owner, group; }; static void chown_internal _((const char *, void *)); static void chown_internal(path, argp) const char *path; void *argp; { struct chown_args *args = (struct chown_args *)argp; if (chown(path, args->owner, args->group) < 0) rb_sys_fail(path); } /* * call-seq: * File.chown(owner_int, group_int, file_name,... ) -> integer * * Changes the owner and group of the named file(s) to the given * numeric owner and group id's. Only a process with superuser * privileges may change the owner of a file. The current owner of a * file may change the file's group to any group to which the owner * belongs. A nil or -1 owner or group id is ignored. * Returns the number of files processed. * * File.chown(nil, 100, "testfile") * */ static VALUE rb_file_s_chown(argc, argv) int argc; VALUE *argv; { VALUE o, g, rest; struct chown_args arg; long n; rb_secure(2); rb_scan_args(argc, argv, "2*", &o, &g, &rest); if (NIL_P(o)) { arg.owner = -1; } else { arg.owner = NUM2INT(o); } if (NIL_P(g)) { arg.group = -1; } else { arg.group = NUM2INT(g); } n = apply2files(chown_internal, rest, &arg); return LONG2FIX(n); } /* * call-seq: * file.chown(owner_int, group_int ) => 0 * * Changes the owner and group of file to the given numeric * owner and group id's. Only a process with superuser privileges may * change the owner of a file. The current owner of a file may change * the file's group to any group to which the owner belongs. A * nil or -1 owner or group id is ignored. Follows * symbolic links. See also File#lchown. * * File.new("testfile").chown(502, 1000) * */ static VALUE rb_file_chown(obj, owner, group) VALUE obj, owner, group; { OpenFile *fptr; int o, g; rb_secure(2); o = NIL_P(owner) ? -1 : NUM2INT(owner); g = NIL_P(group) ? -1 : NUM2INT(group); GetOpenFile(obj, fptr); #if defined(DJGPP) || defined(__CYGWIN32__) || defined(_WIN32) || defined(__EMX__) if (!fptr->path) return Qnil; if (chown(fptr->path, o, g) == -1) rb_sys_fail(fptr->path); #else if (fchown(fileno(fptr->f), o, g) == -1) rb_sys_fail(fptr->path); #endif return INT2FIX(0); } #if defined(HAVE_LCHOWN) && !defined(__CHECKER__) static void lchown_internal _((const char *, void *)); static void lchown_internal(path, argp) const char *path; void *argp; { struct chown_args *args = (struct chown_args *)argp; if (lchown(path, args->owner, args->group) < 0) rb_sys_fail(path); } /* * call-seq: * file.lchown(owner_int, group_int, file_name,..) => integer * * Equivalent to File::chown, but does not follow symbolic * links (so it will change the owner associated with the link, not the * file referenced by the link). Often not available. Returns number * of files in the argument list. * */ static VALUE rb_file_s_lchown(argc, argv) int argc; VALUE *argv; { VALUE o, g, rest; struct chown_args arg; long n; rb_secure(2); rb_scan_args(argc, argv, "2*", &o, &g, &rest); if (NIL_P(o)) { arg.owner = -1; } else { arg.owner = NUM2INT(o); } if (NIL_P(g)) { arg.group = -1; } else { arg.group = NUM2INT(g); } n = apply2files(lchown_internal, rest, &arg); return LONG2FIX(n); } #else static VALUE rb_file_s_lchown(argc, argv) int argc; VALUE *argv; { rb_notimplement(); } #endif struct timeval rb_time_timeval(); static void utime_internal _((const char *, void *)); #if defined(HAVE_UTIMES) && !defined(__CHECKER__) static void utime_internal(path, arg) const char *path; void *arg; { struct timeval *tvp = arg; if (utimes(path, tvp) < 0) rb_sys_fail(path); } /* * call-seq: * File.utime(atime, mtime, file_name,...) => integer * * Sets the access and modification times of each * named file to the first two arguments. Returns * the number of file names in the argument list. */ static VALUE rb_file_s_utime(argc, argv) int argc; VALUE *argv; { VALUE atime, mtime, rest; struct timeval tvs[2], *tvp = NULL; long n; rb_scan_args(argc, argv, "2*", &atime, &mtime, &rest); if (!NIL_P(atime) || !NIL_P(mtime)) { tvp = tvs; tvp[0] = rb_time_timeval(atime); tvp[1] = rb_time_timeval(mtime); } n = apply2files(utime_internal, rest, tvp); return LONG2FIX(n); } #else #if !defined HAVE_UTIME_H && !defined HAVE_SYS_UTIME_H struct utimbuf { long actime; long modtime; }; #endif static void utime_internal(path, arg) const char *path; void *arg; { struct utimbuf *utp = arg; if (utime(path, utp) < 0) rb_sys_fail(path); } static VALUE rb_file_s_utime(argc, argv) int argc; VALUE *argv; { VALUE atime, mtime, rest; long n; struct timeval tv; struct utimbuf utbuf, *utp = NULL; rb_scan_args(argc, argv, "2*", &atime, &mtime, &rest); if (!NIL_P(atime) || !NIL_P(mtime)) { utp = &utbuf; tv = rb_time_timeval(atime); utp->actime = tv.tv_sec; tv = rb_time_timeval(mtime); utp->modtime = tv.tv_sec; } n = apply2files(utime_internal, rest, utp); return LONG2FIX(n); } #endif NORETURN(static void sys_fail2 _((VALUE,VALUE))); static void sys_fail2(s1, s2) VALUE s1, s2; { char *buf; int len; len = RSTRING(s1)->len + RSTRING(s2)->len + 5; buf = ALLOCA_N(char, len); snprintf(buf, len, "%s or %s", RSTRING(s1)->ptr, RSTRING(s2)->ptr); rb_sys_fail(buf); } /* * call-seq: * File.link(old_name, new_name) => 0 * * Creates a new name for an existing file using a hard link. Will not * overwrite new_name if it already exists (raising a subclass * of SystemCallError). Not available on all platforms. * * File.link("testfile", ".testfile") #=> 0 * IO.readlines(".testfile")[0] #=> "This is line one\n" */ static VALUE rb_file_s_link(klass, from, to) VALUE klass, from, to; { #ifdef HAVE_LINK SafeStringValue(from); SafeStringValue(to); if (link(StringValueCStr(from), StringValueCStr(to)) < 0) { sys_fail2(from, to); } return INT2FIX(0); #else rb_notimplement(); return Qnil; /* not reached */ #endif } /* * call-seq: * File.symlink(old_name, new_name) => 0 * * Creates a symbolic link called new_name for the existing file * old_name. Raises a NotImplemented exception on * platforms that do not support symbolic links. * * File.symlink("testfile", "link2test") #=> 0 * */ static VALUE rb_file_s_symlink(klass, from, to) VALUE klass, from, to; { #ifdef HAVE_SYMLINK SafeStringValue(from); SafeStringValue(to); if (symlink(StringValueCStr(from), StringValueCStr(to)) < 0) { sys_fail2(from, to); } return INT2FIX(0); #else rb_notimplement(); return Qnil; /* not reached */ #endif } /* * call-seq: * File.readlink(link_name) -> file_name * * Returns the name of the file referenced by the given link. * Not available on all platforms. * * File.symlink("testfile", "link2test") #=> 0 * File.readlink("link2test") #=> "testfile" */ static VALUE rb_file_s_readlink(klass, path) VALUE klass, path; { #ifdef HAVE_READLINK char *buf; int size = 100; int rv; VALUE v; SafeStringValue(path); buf = xmalloc(size); while ((rv = readlink(RSTRING(path)->ptr, buf, size)) == size #ifdef _AIX || (rv < 0 && errno == ERANGE) /* quirky behavior of GPFS */ #endif ) { size *= 2; buf = xrealloc(buf, size); } if (rv < 0) { free(buf); rb_sys_fail(RSTRING(path)->ptr); } v = rb_tainted_str_new(buf, rv); free(buf); return v; #else rb_notimplement(); return Qnil; /* not reached */ #endif } static void unlink_internal _((const char *, void *)); static void unlink_internal(path, arg) const char *path; void *arg; { if (unlink(path) < 0) rb_sys_fail(path); } /* * call-seq: * File.delete(file_name, ...) => integer * File.unlink(file_name, ...) => integer * * Deletes the named files, returning the number of names * passed as arguments. Raises an exception on any error. * See also Dir::rmdir. */ static VALUE rb_file_s_unlink(klass, args) VALUE klass, args; { long n; rb_secure(2); n = apply2files(unlink_internal, args, 0); return LONG2FIX(n); } /* * call-seq: * File.rename(old_name, new_name) => 0 * * Renames the given file to the new name. Raises a * SystemCallError if the file cannot be renamed. * * File.rename("afile", "afile.bak") #=> 0 */ static VALUE rb_file_s_rename(klass, from, to) VALUE klass, from, to; { const char *src, *dst; SafeStringValue(from); SafeStringValue(to); src = StringValueCStr(from); dst = StringValueCStr(to); #if defined __CYGWIN__ errno = 0; #endif if (rename(src, dst) < 0) { #if defined DOSISH && !defined _WIN32 switch (errno) { case EEXIST: #if defined (__EMX__) case EACCES: #endif if (chmod(dst, 0666) == 0 && unlink(dst) == 0 && rename(src, dst) == 0) return INT2FIX(0); } #endif sys_fail2(from, to); } return INT2FIX(0); } /* * call-seq: * File.umask() => integer * File.umask(integer) => integer * * Returns the current umask value for this process. If the optional * argument is given, set the umask to that value and return the * previous value. Umask values are subtracted from the * default permissions, so a umask of 0222 would make a * file read-only for everyone. * * File.umask(0006) #=> 18 * File.umask #=> 6 */ static VALUE rb_file_s_umask(argc, argv) int argc; VALUE *argv; { int omask = 0; rb_secure(2); if (argc == 0) { omask = umask(0); umask(omask); } else if (argc == 1) { omask = umask(NUM2INT(argv[0])); } else { rb_raise(rb_eArgError, "wrong number of arguments"); } return INT2FIX(omask); } #ifdef __CYGWIN__ #undef DOSISH #endif #if defined __CYGWIN__ || defined DOSISH #define DOSISH_UNC #define DOSISH_DRIVE_LETTER #define isdirsep(x) ((x) == '/' || (x) == '\\') #else #define isdirsep(x) ((x) == '/') #endif #if defined _WIN32 || defined __CYGWIN__ #define USE_NTFS 1 #else #define USE_NTFS 0 #endif #if USE_NTFS #define istrailinggabage(x) ((x) == '.' || (x) == ' ') #else #define istrailinggabage(x) 0 #endif #ifndef CharNext /* defined as CharNext[AW] on Windows. */ # if defined(DJGPP) # define CharNext(p) ((p) + mblen(p, MB_CUR_MAX)) # else # define CharNext(p) ((p) + 1) # endif #endif #ifdef DOSISH_DRIVE_LETTER static inline int has_drive_letter(buf) const char *buf; { if (ISALPHA(buf[0]) && buf[1] == ':') { return 1; } else { return 0; } } static char* getcwdofdrv(drv) int drv; { char drive[4]; char *drvcwd, *oldcwd; drive[0] = drv; drive[1] = ':'; drive[2] = '\0'; /* the only way that I know to get the current directory of a particular drive is to change chdir() to that drive, so save the old cwd before chdir() */ oldcwd = my_getcwd(); if (chdir(drive) == 0) { drvcwd = my_getcwd(); chdir(oldcwd); free(oldcwd); } else { /* perhaps the drive is not exist. we return only drive letter */ drvcwd = strdup(drive); } return drvcwd; } #endif static inline char * skiproot(path) const char *path; { #ifdef DOSISH_DRIVE_LETTER if (has_drive_letter(path)) path += 2; #endif while (isdirsep(*path)) path++; return (char *)path; } #define nextdirsep rb_path_next char * rb_path_next(s) const char *s; { while (*s && !isdirsep(*s)) { s = CharNext(s); } return (char *)s; } #if defined(DOSISH_UNC) || defined(DOSISH_DRIVE_LETTER) #define skipprefix rb_path_skip_prefix #else #define skipprefix(path) (path) #endif char * rb_path_skip_prefix(path) const char *path; { #if defined(DOSISH_UNC) || defined(DOSISH_DRIVE_LETTER) #ifdef DOSISH_UNC if (isdirsep(path[0]) && isdirsep(path[1])) { path += 2; while (isdirsep(*path)) path++; if (*(path = nextdirsep(path)) && path[1] && !isdirsep(path[1])) path = nextdirsep(path + 1); return (char *)path; } #endif #ifdef DOSISH_DRIVE_LETTER if (has_drive_letter(path)) return (char *)(path + 2); #endif #endif return (char *)path; } #define strrdirsep rb_path_last_separator char * rb_path_last_separator(path) const char *path; { char *last = NULL; while (*path) { if (isdirsep(*path)) { const char *tmp = path++; while (isdirsep(*path)) path++; if (!*path) break; last = (char *)tmp; } else { path = CharNext(path); } } return last; } static char * chompdirsep(path) const char *path; { while (*path) { if (isdirsep(*path)) { const char *last = path++; while (isdirsep(*path)) path++; if (!*path) return (char *)last; } else { path = CharNext(path); } } return (char *)path; } char * rb_path_end(path) const char *path; { if (isdirsep(*path)) path++; return chompdirsep(path); } #if USE_NTFS static char * ntfs_tail(const char *path) { while (*path == '.') path++; while (*path && *path != ':') { if (istrailinggabage(*path)) { const char *last = path++; while (istrailinggabage(*path)) path++; if (!*path || *path == ':') return (char *)last; } else if (isdirsep(*path)) { const char *last = path++; while (isdirsep(*path)) path++; if (!*path) return (char *)last; if (*path == ':') path++; } else { path = CharNext(path); } } return (char *)path; } #endif #define BUFCHECK(cond) do {\ long bdiff = p - buf;\ if (cond) {\ do {buflen *= 2;} while (cond);\ rb_str_resize(result, buflen);\ buf = RSTRING(result)->ptr;\ p = buf + bdiff;\ pend = buf + buflen;\ }\ } while (0) #define BUFINIT() (\ p = buf = RSTRING(result)->ptr,\ buflen = RSTRING(result)->len,\ pend = p + buflen) #if !defined(TOLOWER) #define TOLOWER(c) (ISUPPER(c) ? tolower(c) : (c)) #endif static int is_absolute_path _((const char*)); static VALUE file_expand_path(fname, dname, result) VALUE fname, dname, result; { const char *s, *b; char *buf, *p, *pend, *root; long buflen, dirlen; int tainted; s = StringValuePtr(fname); BUFINIT(); tainted = OBJ_TAINTED(fname); if (s[0] == '~') { if (isdirsep(s[1]) || s[1] == '\0') { char *dir = getenv("HOME"); if (!dir) { rb_raise(rb_eArgError, "couldn't find HOME environment -- expanding `%s'", s); } dirlen = strlen(dir); BUFCHECK(dirlen > buflen); strcpy(buf, dir); #if defined DOSISH || defined __CYGWIN__ for (p = buf; *p; p = CharNext(p)) { if (*p == '\\') { *p = '/'; } } #else p = buf + strlen(dir); #endif s++; tainted = 1; } else { #ifdef HAVE_PWD_H struct passwd *pwPtr; s++; #endif s = nextdirsep(b = s); BUFCHECK(bdiff + (s-b) >= buflen); memcpy(p, b, s-b); p += s-b; *p = '\0'; #ifdef HAVE_PWD_H pwPtr = getpwnam(buf); if (!pwPtr) { endpwent(); rb_raise(rb_eArgError, "user %s doesn't exist", buf); } dirlen = strlen(pwPtr->pw_dir); BUFCHECK(dirlen > buflen); strcpy(buf, pwPtr->pw_dir); p = buf + strlen(pwPtr->pw_dir); endpwent(); #endif } } #ifdef DOSISH_DRIVE_LETTER /* skip drive letter */ else if (has_drive_letter(s)) { if (isdirsep(s[2])) { /* specified drive letter, and full path */ /* skip drive letter */ BUFCHECK(bdiff + 2 >= buflen); memcpy(p, s, 2); p += 2; s += 2; } else { /* specified drive, but not full path */ int same = 0; if (!NIL_P(dname)) { file_expand_path(dname, Qnil, result); BUFINIT(); if (has_drive_letter(p) && TOLOWER(p[0]) == TOLOWER(s[0])) { /* ok, same drive */ same = 1; } } if (!same) { char *dir = getcwdofdrv(*s); tainted = 1; dirlen = strlen(dir); BUFCHECK(dirlen > buflen); strcpy(buf, dir); free(dir); } p = chompdirsep(skiproot(buf)); s += 2; } } #endif else if (!is_absolute_path(s)) { if (!NIL_P(dname)) { file_expand_path(dname, Qnil, result); BUFINIT(); } else { char *dir = my_getcwd(); tainted = 1; dirlen = strlen(dir); BUFCHECK(dirlen > buflen); strcpy(buf, dir); free(dir); } #if defined DOSISH || defined __CYGWIN__ if (isdirsep(*s)) { /* specified full path, but not drive letter nor UNC */ /* we need to get the drive letter or UNC share name */ p = skipprefix(buf); } else #endif p = chompdirsep(skiproot(buf)); } else { b = s; do s++; while (isdirsep(*s)); p = buf + (s - b); BUFCHECK(bdiff >= buflen); memset(buf, '/', p - buf); } if (p > buf && p[-1] == '/') --p; else *p = '/'; p[1] = 0; root = skipprefix(buf); b = s; while (*s) { switch (*s) { case '.': if (b == s++) { /* beginning of path element */ switch (*s) { case '\0': b = s; break; case '.': if (*(s+1) == '\0' || isdirsep(*(s+1))) { /* We must go back to the parent */ char *n; *p = '\0'; if (!(n = strrdirsep(root))) { *p = '/'; } else { p = n; } b = ++s; } #if USE_NTFS else { do *++s; while (istrailinggabage(*s)); } #endif break; case '/': #if defined DOSISH || defined __CYGWIN__ case '\\': #endif b = ++s; break; default: /* ordinary path element, beginning don't move */ break; } } #if USE_NTFS else { --s; case ' ': { const char *e = s; while (istrailinggabage(*s)) s++; if (!*s) { s = e; goto endpath; } } } #endif break; case '/': #if defined DOSISH || defined __CYGWIN__ case '\\': #endif if (s > b) { long rootdiff = root - buf; BUFCHECK(bdiff + (s-b+1) >= buflen); root = buf + rootdiff; memcpy(++p, b, s-b); p += s-b; *p = '/'; } b = ++s; break; default: s = CharNext(s); break; } } if (s > b) { #if USE_NTFS endpath: if (s > b + 6 && strncasecmp(s - 6, ":$DATA", 6) == 0) { /* alias of stream */ /* get rid of a bug of x64 VC++ */ if (*(s-7) == ':') s -= 7; /* prime */ else if (memchr(b, ':', s - 6 - b)) s -= 6; /* alternative */ } #endif BUFCHECK(bdiff + (s-b) >= buflen); memcpy(++p, b, s-b); p += s-b; } if (p == skiproot(buf) - 1) p++; #if USE_NTFS *p = '\0'; if ((s = strrdirsep(b = buf)) != 0 && !strpbrk(s, "*?")) { size_t len; WIN32_FIND_DATA wfd; #ifdef __CYGWIN__ int lnk_added = 0, is_symlink = 0; struct stat st; char w32buf[MAXPATHLEN]; p = (char *)s; if (lstat(buf, &st) == 0 && S_ISLNK(st.st_mode)) { is_symlink = 1; *p = '\0'; } if (cygwin_conv_to_win32_path((*buf ? buf : "/"), w32buf) == 0) { b = w32buf; } if (is_symlink && b == w32buf) { *p = '\\'; strlcat(w32buf, p, sizeof(w32buf)); len = strlen(p); if (len > 4 && strcasecmp(p + len - 4, ".lnk") != 0) { lnk_added = 1; strlcat(w32buf, ".lnk", sizeof(w32buf)); } } *p = '/'; #endif HANDLE h = FindFirstFile(b, &wfd); if (h != INVALID_HANDLE_VALUE) { FindClose(h); len = strlen(wfd.cFileName); #ifdef __CYGWIN__ if (lnk_added && len > 4 && strcasecmp(wfd.cFileName + len - 4, ".lnk") == 0) { wfd.cFileName[len -= 4] = '\0'; } #else p = (char *)s; #endif ++p; BUFCHECK(bdiff + len >= buflen); memcpy(p, wfd.cFileName, len + 1); p += len; } #ifdef __CYGWIN__ else { p += strlen(p); } #endif } #endif if (tainted) OBJ_TAINT(result); RSTRING(result)->len = p - buf; RSTRING(result)->ptr[p - buf] = '\0'; return result; } VALUE rb_file_expand_path(fname, dname) VALUE fname, dname; { return file_expand_path(fname, dname, rb_str_new(0, MAXPATHLEN + 2)); } /* * call-seq: * File.expand_path(file_name [, dir_string] ) -> abs_file_name * * Converts a pathname to an absolute pathname. Relative paths are * referenced from the current working directory of the process unless * dir_string is given, in which case it will be used as the * starting point. The given pathname may start with a * ``~'', which expands to the process owner's home * directory (the environment variable HOME must be set * correctly). ``~user'' expands to the named * user's home directory. * * File.expand_path("~oracle/bin") #=> "/home/oracle/bin" * File.expand_path("../../bin", "/tmp/x") #=> "/bin" */ VALUE rb_file_s_expand_path(argc, argv) int argc; VALUE *argv; { VALUE fname, dname; if (argc == 1) { return rb_file_expand_path(argv[0], Qnil); } rb_scan_args(argc, argv, "11", &fname, &dname); return rb_file_expand_path(fname, dname); } static int rmext(p, l1, e) const char *p, *e; int l1; { int l2; if (!e) return 0; l2 = strlen(e); if (l2 == 2 && e[1] == '*') { unsigned char c = *e; e = p + l1; do { if (e <= p) return 0; } while (*--e != c); return e - p; } if (l1 < l2) return l1; #if CASEFOLD_FILESYSTEM #define fncomp strncasecmp #else #define fncomp strncmp #endif if (fncomp(p+l1-l2, e, l2) == 0) { return l1-l2; } return 0; } /* * call-seq: * File.basename(file_name [, suffix] ) -> base_name * * Returns the last component of the filename given in file_name, * which must be formed using forward slashes (``/'') * regardless of the separator used on the local file system. If * suffix is given and present at the end of file_name, * it is removed. * * File.basename("/home/gumby/work/ruby.rb") #=> "ruby.rb" * File.basename("/home/gumby/work/ruby.rb", ".rb") #=> "ruby" */ static VALUE rb_file_s_basename(argc, argv) int argc; VALUE *argv; { VALUE fname, fext, basename; char *name, *p; #if defined DOSISH_DRIVE_LETTER || defined DOSISH_UNC char *root; #endif int f, n; if (rb_scan_args(argc, argv, "11", &fname, &fext) == 2) { StringValue(fext); } StringValue(fname); if (RSTRING(fname)->len == 0 || !*(name = RSTRING(fname)->ptr)) return fname; name = skipprefix(name); #if defined DOSISH_DRIVE_LETTER || defined DOSISH_UNC root = name; #endif while (isdirsep(*name)) name++; if (!*name) { p = name - 1; f = 1; #if defined DOSISH_DRIVE_LETTER || defined DOSISH_UNC if (name != root) { /* has slashes */ } #ifdef DOSISH_DRIVE_LETTER else if (*p == ':') { p++; f = 0; } #endif #ifdef DOSISH_UNC else { p = "/"; } #endif #endif } else { if (!(p = strrdirsep(name))) { p = name; } else { while (isdirsep(*p)) p++; /* skip last / */ } #if USE_NTFS n = ntfs_tail(p) - p; #else n = chompdirsep(p) - p; #endif if (NIL_P(fext) || !(f = rmext(p, n, StringValueCStr(fext)))) { f = n; } if (f == RSTRING_LEN(fname)) return fname; } basename = rb_str_new(p, f); OBJ_INFECT(basename, fname); return basename; } /* * call-seq: * File.dirname(file_name ) -> dir_name * * Returns all components of the filename given in file_name * except the last one. The filename must be formed using forward * slashes (``/'') regardless of the separator used on the * local file system. * * File.dirname("/home/gumby/work/ruby.rb") #=> "/home/gumby/work" */ static VALUE rb_file_s_dirname(klass, fname) VALUE klass, fname; { const char *name, *root, *p; VALUE dirname; name = StringValueCStr(fname); root = skiproot(name); #ifdef DOSISH_UNC if (root > name + 1 && isdirsep(*name)) root = skipprefix(name = root - 2); #else if (root > name + 1) name = root - 1; #endif p = strrdirsep(root); if (!p) { p = root; } if (p == name) return rb_str_new2("."); #ifdef DOSISH_DRIVE_LETTER if (has_drive_letter(name) && isdirsep(*(name + 2))) { const char *top = skiproot(name + 2); dirname = rb_str_new(name, 3); rb_str_cat(dirname, top, p - top); } else #endif dirname = rb_str_new(name, p - name); #ifdef DOSISH_DRIVE_LETTER if (has_drive_letter(name) && root == name + 2 && p - name == 2) rb_str_cat(dirname, ".", 1); #endif OBJ_INFECT(dirname, fname); return dirname; } /* * call-seq: * File.extname(path) -> string * * Returns the extension (the portion of file name in path * after the period). * * File.extname("test.rb") #=> ".rb" * File.extname("a/b/d/test.rb") #=> ".rb" * File.extname("test") #=> "" * File.extname(".profile") #=> "" * */ static VALUE rb_file_s_extname(klass, fname) VALUE klass, fname; { const char *name, *p, *e; VALUE extname; name = StringValueCStr(fname); p = strrdirsep(name); /* get the last path component */ if (!p) p = name; else name = ++p; e = 0; while (*p) { if (*p == '.' || istrailinggabage(*p)) { #if USE_NTFS const char *last = p++, *dot = last; while (istrailinggabage(*p)) { if (*p == '.') dot = p; p++; } if (!*p || *p == ':') { p = last; break; } if (*last == '.' || dot > last) e = dot; continue; #else e = p; /* get the last dot of the last component */ #endif } #if USE_NTFS else if (*p == ':') { break; } #endif else if (isdirsep(*p)) break; p = CharNext(p); } if (!e || e == name || e+1 == p) /* no dot, or the only dot is first or end? */ return rb_str_new(0, 0); extname = rb_str_new(e, p - e); /* keep the dot, too! */ OBJ_INFECT(extname, fname); return extname; } /* * call-seq: * File.split(file_name) => array * * Splits the given string into a directory and a file component and * returns them in a two-element array. See also * File::dirname and File::basename. * * File.split("/home/gumby/.profile") #=> ["/home/gumby", ".profile"] */ static VALUE rb_file_s_split(klass, path) VALUE klass, path; { StringValue(path); /* get rid of converting twice */ return rb_assoc_new(rb_file_s_dirname(Qnil, path), rb_file_s_basename(1,&path)); } static VALUE separator; static VALUE rb_file_join _((VALUE ary, VALUE sep)); static VALUE file_inspect_join(ary, arg) VALUE ary; VALUE *arg; { return rb_file_join(arg[0], arg[1]); } static VALUE rb_file_join(ary, sep) VALUE ary, sep; { long len, i; int taint = 0; VALUE result, tmp; char *name, *tail; if (RARRAY(ary)->len == 0) return rb_str_new(0, 0); if (OBJ_TAINTED(ary)) taint = 1; if (OBJ_TAINTED(sep)) taint = 1; len = 1; for (i=0; ilen; i++) { if (TYPE(RARRAY(ary)->ptr[i]) == T_STRING) { len += RSTRING(RARRAY(ary)->ptr[i])->len; } else { len += 10; } } if (!NIL_P(sep) && TYPE(sep) == T_STRING) { len += RSTRING(sep)->len * RARRAY(ary)->len - 1; } result = rb_str_buf_new(len); for (i=0; ilen; i++) { tmp = RARRAY(ary)->ptr[i]; switch (TYPE(tmp)) { case T_STRING: break; case T_ARRAY: if (rb_inspecting_p(tmp)) { tmp = rb_str_new2("[...]"); } else { VALUE args[2]; args[0] = tmp; args[1] = sep; tmp = rb_protect_inspect(file_inspect_join, ary, (VALUE)args); } break; default: StringValueCStr(tmp); } name = StringValueCStr(result); if (i > 0 && !NIL_P(sep)) { tail = chompdirsep(name); if (RSTRING(tmp)->ptr && isdirsep(RSTRING(tmp)->ptr[0])) { RSTRING(result)->len = tail - name; } else if (!*tail) { rb_str_buf_append(result, sep); } } rb_str_buf_append(result, tmp); if (OBJ_TAINTED(tmp)) taint = 1; } if (taint) OBJ_TAINT(result); return result; } /* * call-seq: * File.join(string, ...) -> path * * Returns a new string formed by joining the strings using * File::SEPARATOR. * * File.join("usr", "mail", "gumby") #=> "usr/mail/gumby" * */ static VALUE rb_file_s_join(klass, args) VALUE klass, args; { return rb_file_join(args, separator); } /* * call-seq: * File.truncate(file_name, integer) => 0 * * Truncates the file file_name to be at most integer * bytes long. Not available on all platforms. * * f = File.new("out", "w") * f.write("1234567890") #=> 10 * f.close #=> nil * File.truncate("out", 5) #=> 0 * File.size("out") #=> 5 * */ static VALUE rb_file_s_truncate(klass, path, len) VALUE klass, path, len; { off_t pos; rb_secure(2); pos = NUM2OFFT(len); SafeStringValue(path); #ifdef HAVE_TRUNCATE if (truncate(StringValueCStr(path), pos) < 0) rb_sys_fail(RSTRING(path)->ptr); #else # ifdef HAVE_CHSIZE { int tmpfd; # ifdef _WIN32 if ((tmpfd = open(StringValueCStr(path), O_RDWR)) < 0) { rb_sys_fail(RSTRING(path)->ptr); } # else if ((tmpfd = open(StringValueCStr(path), 0)) < 0) { rb_sys_fail(RSTRING(path)->ptr); } # endif if (chsize(tmpfd, pos) < 0) { close(tmpfd); rb_sys_fail(RSTRING(path)->ptr); } close(tmpfd); } # else rb_notimplement(); # endif #endif return INT2FIX(0); } /* * call-seq: * file.truncate(integer) => 0 * * Truncates file to at most integer bytes. The file * must be opened for writing. Not available on all platforms. * * f = File.new("out", "w") * f.syswrite("1234567890") #=> 10 * f.truncate(5) #=> 0 * f.close() #=> nil * File.size("out") #=> 5 */ static VALUE rb_file_truncate(obj, len) VALUE obj, len; { OpenFile *fptr; FILE *f; off_t pos; rb_secure(2); pos = NUM2OFFT(len); GetOpenFile(obj, fptr); if (!(fptr->mode & FMODE_WRITABLE)) { rb_raise(rb_eIOError, "not opened for writing"); } f = GetWriteFile(fptr); fflush(f); fseeko(f, (off_t)0, SEEK_CUR); #ifdef HAVE_FTRUNCATE if (ftruncate(fileno(f), pos) < 0) rb_sys_fail(fptr->path); #else # ifdef HAVE_CHSIZE if (chsize(fileno(f), pos) < 0) rb_sys_fail(fptr->path); # else rb_notimplement(); # endif #endif return INT2FIX(0); } # ifndef LOCK_SH # define LOCK_SH 1 # endif # ifndef LOCK_EX # define LOCK_EX 2 # endif # ifndef LOCK_NB # define LOCK_NB 4 # endif # ifndef LOCK_UN # define LOCK_UN 8 # endif #ifdef __CYGWIN__ #include extern unsigned long __attribute__((stdcall)) GetLastError(void); static int cygwin_flock(int fd, int op) { int old_errno = errno; int ret = flock(fd, op); if (GetLastError() == ERROR_NOT_LOCKED) { ret = 0; errno = old_errno; } return ret; } # define flock(fd, op) cygwin_flock(fd, op) #endif static int rb_thread_flock(fd, op, fptr) int fd, op; OpenFile *fptr; { if (rb_thread_alone() || (op & LOCK_NB)) { int ret; TRAP_BEG; ret = flock(fd, op); TRAP_END; return ret; } op |= LOCK_NB; while (flock(fd, op) < 0) { switch (errno) { case EAGAIN: case EACCES: #if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN case EWOULDBLOCK: #endif rb_thread_polling(); /* busy wait */ rb_io_check_closed(fptr); continue; default: return -1; } } return 0; } #ifdef __CYGWIN__ # undef flock #endif #define flock(fd, op) rb_thread_flock(fd, op, fptr) /* * call-seq: * file.flock (locking_constant ) => 0 or false * * Locks or unlocks a file according to locking_constant (a * logical or of the values in the table below). * Returns false if File::LOCK_NB is * specified and the operation would otherwise have blocked. Not * available on all platforms. * * Locking constants (in class File): * * LOCK_EX | Exclusive lock. Only one process may hold an * | exclusive lock for a given file at a time. * ----------+------------------------------------------------ * LOCK_NB | Don't block when locking. May be combined * | with other lock options using logical or. * ----------+------------------------------------------------ * LOCK_SH | Shared lock. Multiple processes may each hold a * | shared lock for a given file at the same time. * ----------+------------------------------------------------ * LOCK_UN | Unlock. * * Example: * * File.new("testfile").flock(File::LOCK_UN) #=> 0 * */ static VALUE rb_file_flock(obj, operation) VALUE obj; VALUE operation; { #ifndef __CHECKER__ OpenFile *fptr; int op; rb_secure(2); op = NUM2INT(operation); GetOpenFile(obj, fptr); if (fptr->mode & FMODE_WRITABLE) { fflush(GetWriteFile(fptr)); } retry: if (flock(fileno(fptr->f), op) < 0) { switch (errno) { case EAGAIN: case EACCES: #if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN case EWOULDBLOCK: #endif return Qfalse; case EINTR: #if defined(ERESTART) case ERESTART: #endif goto retry; } rb_sys_fail(fptr->path); } #endif return INT2FIX(0); } #undef flock static void test_check(n, argc, argv) int n, argc; VALUE *argv; { int i; n+=1; if (n != argc) rb_raise(rb_eArgError, "wrong number of arguments (%d for %d)", argc, n); for (i=1; i obj * * Uses the integer aCmd to perform various tests on * file1 (first table below) or on file1 and * file2 (second table). * * File tests on a single file: * * Test Returns Meaning * ?A | Time | Last access time for file1 * ?b | boolean | True if file1 is a block device * ?c | boolean | True if file1 is a character device * ?C | Time | Last change time for file1 * ?d | boolean | True if file1 exists and is a directory * ?e | boolean | True if file1 exists * ?f | boolean | True if file1 exists and is a regular file * ?g | boolean | True if file1 has the \CF{setgid} bit * | | set (false under NT) * ?G | boolean | True if file1 exists and has a group * | | ownership equal to the caller's group * ?k | boolean | True if file1 exists and has the sticky bit set * ?l | boolean | True if file1 exists and is a symbolic link * ?M | Time | Last modification time for file1 * ?o | boolean | True if file1 exists and is owned by * | | the caller's effective uid * ?O | boolean | True if file1 exists and is owned by * | | the caller's real uid * ?p | boolean | True if file1 exists and is a fifo * ?r | boolean | True if file1 is readable by the effective * | | uid/gid of the caller * ?R | boolean | True if file is readable by the real * | | uid/gid of the caller * ?s | int/nil | If file1 has nonzero size, return the size, * | | otherwise return nil * ?S | boolean | True if file1 exists and is a socket * ?u | boolean | True if file1 has the setuid bit set * ?w | boolean | True if file1 exists and is writable by * | | the effective uid/gid * ?W | boolean | True if file1 exists and is writable by * | | the real uid/gid * ?x | boolean | True if file1 exists and is executable by * | | the effective uid/gid * ?X | boolean | True if file1 exists and is executable by * | | the real uid/gid * ?z | boolean | True if file1 exists and has a zero length * * Tests that take two files: * * ?- | boolean | True if file1 and file2 are identical * ?= | boolean | True if the modification times of file1 * | | and file2 are equal * ?< | boolean | True if the modification time of file1 * | | is prior to that of file2 * ?> | boolean | True if the modification time of file1 * | | is after that of file2 */ static VALUE rb_f_test(argc, argv) int argc; VALUE *argv; { int cmd; if (argc == 0) rb_raise(rb_eArgError, "wrong number of arguments"); #if 0 /* 1.7 behavior? */ if (argc == 1) { return RTEST(argv[0]) ? Qtrue : Qfalse; } #endif cmd = NUM2CHR(argv[0]); if (cmd == 0) return Qfalse; if (strchr("bcdefgGkloOprRsSuwWxXz", cmd)) { CHECK(1); switch (cmd) { case 'b': return test_b(0, argv[1]); case 'c': return test_c(0, argv[1]); case 'd': return test_d(0, argv[1]); case 'a': case 'e': return test_e(0, argv[1]); case 'f': return test_f(0, argv[1]); case 'g': return test_sgid(0, argv[1]); case 'G': return test_grpowned(0, argv[1]); case 'k': return test_sticky(0, argv[1]); case 'l': return test_l(0, argv[1]); case 'o': return test_owned(0, argv[1]); case 'O': return test_rowned(0, argv[1]); case 'p': return test_p(0, argv[1]); case 'r': return test_r(0, argv[1]); case 'R': return test_R(0, argv[1]); case 's': return test_s(0, argv[1]); case 'S': return test_S(0, argv[1]); case 'u': return test_suid(0, argv[1]); case 'w': return test_w(0, argv[1]); case 'W': return test_W(0, argv[1]); case 'x': return test_x(0, argv[1]); case 'X': return test_X(0, argv[1]); case 'z': return test_z(0, argv[1]); } } if (strchr("MAC", cmd)) { struct stat st; CHECK(1); if (rb_stat(argv[1], &st) == -1) { rb_sys_fail(RSTRING(argv[1])->ptr); } switch (cmd) { case 'A': return rb_time_new(st.st_atime, 0); case 'M': return rb_time_new(st.st_mtime, 0); case 'C': return rb_time_new(st.st_ctime, 0); } } if (cmd == '-') { CHECK(2); return test_identical(0, argv[1], argv[2]); } if (strchr("=<>", cmd)) { struct stat st1, st2; CHECK(2); if (rb_stat(argv[1], &st1) < 0) return Qfalse; if (rb_stat(argv[2], &st2) < 0) return Qfalse; switch (cmd) { case '=': if (st1.st_mtime == st2.st_mtime) return Qtrue; return Qfalse; case '>': if (st1.st_mtime > st2.st_mtime) return Qtrue; return Qfalse; case '<': if (st1.st_mtime < st2.st_mtime) return Qtrue; return Qfalse; } } /* unknown command */ rb_raise(rb_eArgError, "unknown command ?%c", cmd); return Qnil; /* not reached */ } /* * Document-class: File::Stat * * Objects of class File::Stat encapsulate common status * information for File objects. The information is * recorded at the moment the File::Stat object is * created; changes made to the file after that point will not be * reflected. File::Stat objects are returned by * IO#stat, File::stat, * File#lstat, and File::lstat. Many of these * methods return platform-specific values, and not all values are * meaningful on all systems. See also Kernel#test. */ static VALUE rb_stat_s_alloc _((VALUE)); static VALUE rb_stat_s_alloc(klass) VALUE klass; { return stat_new_0(klass, 0); } /* * call-seq: * * File::Stat.new(file_name) => stat * * Create a File::Stat object for the given file name (raising an * exception if the file doesn't exist). */ static VALUE rb_stat_init(obj, fname) VALUE obj, fname; { struct stat st, *nst; SafeStringValue(fname); if (stat(StringValueCStr(fname), &st) == -1) { rb_sys_fail(RSTRING(fname)->ptr); } if (DATA_PTR(obj)) { free(DATA_PTR(obj)); DATA_PTR(obj) = NULL; } nst = ALLOC(struct stat); *nst = st; DATA_PTR(obj) = nst; return Qnil; } /* :nodoc: */ static VALUE rb_stat_init_copy(copy, orig) VALUE copy, orig; { struct stat *nst; if (copy == orig) return orig; rb_check_frozen(copy); /* need better argument type check */ if (!rb_obj_is_instance_of(orig, rb_obj_class(copy))) { rb_raise(rb_eTypeError, "wrong argument class"); } if (DATA_PTR(copy)) { free(DATA_PTR(copy)); DATA_PTR(copy) = 0; } if (DATA_PTR(orig)) { nst = ALLOC(struct stat); *nst = *(struct stat*)DATA_PTR(orig); DATA_PTR(copy) = nst; } return copy; } /* * call-seq: * stat.ftype => string * * Identifies the type of stat. The return string is one of: * ``file'', ``directory'', * ``characterSpecial'', ``blockSpecial'', * ``fifo'', ``link'', * ``socket'', or ``unknown''. * * File.stat("/dev/tty").ftype #=> "characterSpecial" * */ static VALUE rb_stat_ftype(obj) VALUE obj; { return rb_file_ftype(get_stat(obj)); } /* * call-seq: * stat.directory? => true or false * * Returns true if stat is a directory, * false otherwise. * * File.stat("testfile").directory? #=> false * File.stat(".").directory? #=> true */ static VALUE rb_stat_d(obj) VALUE obj; { if (S_ISDIR(get_stat(obj)->st_mode)) return Qtrue; return Qfalse; } /* * call-seq: * stat.pipe? => true or false * * Returns true if the operating system supports pipes and * stat is a pipe; false otherwise. */ static VALUE rb_stat_p(obj) VALUE obj; { #ifdef S_IFIFO if (S_ISFIFO(get_stat(obj)->st_mode)) return Qtrue; #endif return Qfalse; } /* * call-seq: * stat.symlink? => true or false * * Returns true if stat is a symbolic link, * false if it isn't or if the operating system doesn't * support this feature. As File::stat automatically * follows symbolic links, symlink? will always be * false for an object returned by * File::stat. * * File.symlink("testfile", "alink") #=> 0 * File.stat("alink").symlink? #=> false * File.lstat("alink").symlink? #=> true * */ static VALUE rb_stat_l(obj) VALUE obj; { #ifdef S_ISLNK if (S_ISLNK(get_stat(obj)->st_mode)) return Qtrue; #endif return Qfalse; } /* * call-seq: * stat.socket? => true or false * * Returns true if stat is a socket, * false if it isn't or if the operating system doesn't * support this feature. * * File.stat("testfile").socket? #=> false * */ static VALUE rb_stat_S(obj) VALUE obj; { #ifdef S_ISSOCK if (S_ISSOCK(get_stat(obj)->st_mode)) return Qtrue; #endif return Qfalse; } /* * call-seq: * stat.blockdev? => true or false * * Returns true if the file is a block device, * false if it isn't or if the operating system doesn't * support this feature. * * File.stat("testfile").blockdev? #=> false * File.stat("/dev/hda1").blockdev? #=> true * */ static VALUE rb_stat_b(obj) VALUE obj; { #ifdef S_ISBLK if (S_ISBLK(get_stat(obj)->st_mode)) return Qtrue; #endif return Qfalse; } /* * call-seq: * stat.chardev? => true or false * * Returns true if the file is a character device, * false if it isn't or if the operating system doesn't * support this feature. * * File.stat("/dev/tty").chardev? #=> true * */ static VALUE rb_stat_c(obj) VALUE obj; { if (S_ISCHR(get_stat(obj)->st_mode)) return Qtrue; return Qfalse; } /* * call-seq: * stat.owned? => true or false * * Returns true if the effective user id of the process is * the same as the owner of stat. * * File.stat("testfile").owned? #=> true * File.stat("/etc/passwd").owned? #=> false * */ static VALUE rb_stat_owned(obj) VALUE obj; { if (get_stat(obj)->st_uid == geteuid()) return Qtrue; return Qfalse; } static VALUE rb_stat_rowned(obj) VALUE obj; { if (get_stat(obj)->st_uid == getuid()) return Qtrue; return Qfalse; } /* * call-seq: * stat.grpowned? => true or false * * Returns true if the effective group id of the process is the same as * the group id of stat. On Windows NT, returns false. * * File.stat("testfile").grpowned? #=> true * File.stat("/etc/passwd").grpowned? #=> false * */ static VALUE rb_stat_grpowned(obj) VALUE obj; { #ifndef _WIN32 if (group_member(get_stat(obj)->st_gid)) return Qtrue; #endif return Qfalse; } /* * call-seq: * stat.readable? => true or false * * Returns true if stat is readable by the * effective user id of this process. * * File.stat("testfile").readable? #=> true * */ static VALUE rb_stat_r(obj) VALUE obj; { struct stat *st = get_stat(obj); #ifdef USE_GETEUID if (geteuid() == 0) return Qtrue; #endif #ifdef S_IRUSR if (rb_stat_owned(obj)) return st->st_mode & S_IRUSR ? Qtrue : Qfalse; #endif #ifdef S_IRGRP if (rb_stat_grpowned(obj)) return st->st_mode & S_IRGRP ? Qtrue : Qfalse; #endif #ifdef S_IROTH if (!(st->st_mode & S_IROTH)) return Qfalse; #endif return Qtrue; } /* * call-seq: * stat.readable_real? -> true or false * * Returns true if stat is readable by the real * user id of this process. * * File.stat("testfile").readable_real? #=> true * */ static VALUE rb_stat_R(obj) VALUE obj; { struct stat *st = get_stat(obj); #ifdef USE_GETEUID if (getuid() == 0) return Qtrue; #endif #ifdef S_IRUSR if (rb_stat_rowned(obj)) return st->st_mode & S_IRUSR ? Qtrue : Qfalse; #endif #ifdef S_IRGRP if (group_member(get_stat(obj)->st_gid)) return st->st_mode & S_IRGRP ? Qtrue : Qfalse; #endif #ifdef S_IROTH if (!(st->st_mode & S_IROTH)) return Qfalse; #endif return Qtrue; } /* * call-seq: * stat.writable? -> true or false * * Returns true if stat is writable by the * effective user id of this process. * * File.stat("testfile").writable? #=> true * */ static VALUE rb_stat_w(obj) VALUE obj; { struct stat *st = get_stat(obj); #ifdef USE_GETEUID if (geteuid() == 0) return Qtrue; #endif #ifdef S_IWUSR if (rb_stat_owned(obj)) return st->st_mode & S_IWUSR ? Qtrue : Qfalse; #endif #ifdef S_IWGRP if (rb_stat_grpowned(obj)) return st->st_mode & S_IWGRP ? Qtrue : Qfalse; #endif #ifdef S_IWOTH if (!(st->st_mode & S_IWOTH)) return Qfalse; #endif return Qtrue; } /* * call-seq: * stat.writable_real? -> true or false * * Returns true if stat is writable by the real * user id of this process. * * File.stat("testfile").writable_real? #=> true * */ static VALUE rb_stat_W(obj) VALUE obj; { struct stat *st = get_stat(obj); #ifdef USE_GETEUID if (getuid() == 0) return Qtrue; #endif #ifdef S_IWUSR if (rb_stat_rowned(obj)) return st->st_mode & S_IWUSR ? Qtrue : Qfalse; #endif #ifdef S_IWGRP if (group_member(get_stat(obj)->st_gid)) return st->st_mode & S_IWGRP ? Qtrue : Qfalse; #endif #ifdef S_IWOTH if (!(st->st_mode & S_IWOTH)) return Qfalse; #endif return Qtrue; } /* * call-seq: * stat.executable? => true or false * * Returns true if stat is executable or if the * operating system doesn't distinguish executable files from * nonexecutable files. The tests are made using the effective owner of * the process. * * File.stat("testfile").executable? #=> false * */ static VALUE rb_stat_x(obj) VALUE obj; { struct stat *st = get_stat(obj); #ifdef USE_GETEUID if (geteuid() == 0) { return st->st_mode & S_IXUGO ? Qtrue : Qfalse; } #endif #ifdef S_IXUSR if (rb_stat_owned(obj)) return st->st_mode & S_IXUSR ? Qtrue : Qfalse; #endif #ifdef S_IXGRP if (rb_stat_grpowned(obj)) return st->st_mode & S_IXGRP ? Qtrue : Qfalse; #endif #ifdef S_IXOTH if (!(st->st_mode & S_IXOTH)) return Qfalse; #endif return Qtrue; } /* * call-seq: * stat.executable_real? => true or false * * Same as executable?, but tests using the real owner of * the process. */ static VALUE rb_stat_X(obj) VALUE obj; { struct stat *st = get_stat(obj); #ifdef USE_GETEUID if (getuid() == 0) { return st->st_mode & S_IXUGO ? Qtrue : Qfalse; } #endif #ifdef S_IXUSR if (rb_stat_rowned(obj)) return st->st_mode & S_IXUSR ? Qtrue : Qfalse; #endif #ifdef S_IXGRP if (group_member(get_stat(obj)->st_gid)) return st->st_mode & S_IXGRP ? Qtrue : Qfalse; #endif #ifdef S_IXOTH if (!(st->st_mode & S_IXOTH)) return Qfalse; #endif return Qtrue; } /* * call-seq: * stat.file? => true or false * * Returns true if stat is a regular file (not * a device file, pipe, socket, etc.). * * File.stat("testfile").file? #=> true * */ static VALUE rb_stat_f(obj) VALUE obj; { if (S_ISREG(get_stat(obj)->st_mode)) return Qtrue; return Qfalse; } /* * call-seq: * stat.zero? => true or false * * Returns true if stat is a zero-length file; * false otherwise. * * File.stat("testfile").zero? #=> false * */ static VALUE rb_stat_z(obj) VALUE obj; { if (get_stat(obj)->st_size == 0) return Qtrue; return Qfalse; } /* * call-seq: * state.size => integer * * Returns the size of stat in bytes. * * File.stat("testfile").size #=> 66 * */ static VALUE rb_stat_s(obj) VALUE obj; { off_t size = get_stat(obj)->st_size; if (size == 0) return Qnil; return OFFT2NUM(size); } /* * call-seq: * stat.setuid? => true or false * * Returns true if stat has the set-user-id * permission bit set, false if it doesn't or if the * operating system doesn't support this feature. * * File.stat("/bin/su").setuid? #=> true */ static VALUE rb_stat_suid(obj) VALUE obj; { #ifdef S_ISUID if (get_stat(obj)->st_mode & S_ISUID) return Qtrue; #endif return Qfalse; } /* * call-seq: * stat.setgid? => true or false * * Returns true if stat has the set-group-id * permission bit set, false if it doesn't or if the * operating system doesn't support this feature. * * File.stat("/usr/sbin/lpc").setgid? #=> true * */ static VALUE rb_stat_sgid(obj) VALUE obj; { #ifdef S_ISGID if (get_stat(obj)->st_mode & S_ISGID) return Qtrue; #endif return Qfalse; } /* * call-seq: * stat.sticky? => true or false * * Returns true if stat has its sticky bit set, * false if it doesn't or if the operating system doesn't * support this feature. * * File.stat("testfile").sticky? #=> false * */ static VALUE rb_stat_sticky(obj) VALUE obj; { #ifdef S_ISVTX if (get_stat(obj)->st_mode & S_ISVTX) return Qtrue; #endif return Qfalse; } VALUE rb_mFConst; void rb_file_const(name, value) const char *name; VALUE value; { rb_define_const(rb_mFConst, name, value); } static int is_absolute_path(path) const char *path; { #ifdef DOSISH_DRIVE_LETTER if (has_drive_letter(path) && isdirsep(path[2])) return 1; #endif #ifdef DOSISH_UNC if (isdirsep(path[0]) && isdirsep(path[1])) return 1; #endif #ifndef DOSISH if (path[0] == '/') return 1; #endif return 0; } #ifndef ENABLE_PATH_CHECK # if defined DOSISH || defined __CYGWIN__ # define ENABLE_PATH_CHECK 0 # else # define ENABLE_PATH_CHECK 1 # endif #endif #if ENABLE_PATH_CHECK static int path_check_0(fpath, execpath) VALUE fpath; int execpath; { struct stat st; char *p0 = StringValueCStr(fpath); char *p = 0, *s; if (!is_absolute_path(p0)) { char *buf = my_getcwd(); VALUE newpath; newpath = rb_str_new2(buf); free(buf); rb_str_cat2(newpath, "/"); rb_str_cat2(newpath, p0); p0 = RSTRING(fpath = newpath)->ptr; } for (;;) { #ifndef S_IWOTH # define S_IWOTH 002 #endif if (stat(p0, &st) == 0 && S_ISDIR(st.st_mode) && (st.st_mode & S_IWOTH) #ifdef S_ISVTX && !(p && execpath && (st.st_mode & S_ISVTX)) #endif ) { rb_warn("Insecure world writable dir %s in %sPATH, mode 0%o", p0, (execpath ? "" : "LOAD_"), st.st_mode); if (p) *p = '/'; return 0; } s = strrdirsep(p0); if (p) *p = '/'; if (!s || s == p0) return 1; p = s; *p = '\0'; } } #endif static int fpath_check(path) char *path; { #if ENABLE_PATH_CHECK return path_check_0(rb_str_new2(path), Qfalse); #else return 1; #endif } int rb_path_check(path) char *path; { #if ENABLE_PATH_CHECK char *p0, *p, *pend; const char sep = PATH_SEP_CHAR; if (!path) return 1; pend = path + strlen(path); p0 = path; p = strchr(path, sep); if (!p) p = pend; for (;;) { if (!path_check_0(rb_str_new(p0, p - p0), Qtrue)) { return 0; /* not safe */ } p0 = p + 1; if (p0 > pend) break; p = strchr(p0, sep); if (!p) p = pend; } #endif return 1; } #if defined(__MACOS__) || defined(riscos) static int is_macos_native_path(path) const char *path; { if (strchr(path, ':')) return 1; return 0; } #endif static int file_load_ok(file) char *file; { int ret = 1; int fd = open(file, O_RDONLY); if (fd == -1) return 0; #if !defined DOSISH { struct stat st; if (fstat(fd, &st) || !S_ISREG(st.st_mode)) { ret = 0; } } #endif (void)close(fd); return ret; } extern VALUE rb_load_path; int rb_find_file_ext(filep, ext) VALUE *filep; const char * const *ext; { char *path, *found; char *f = RSTRING(*filep)->ptr; VALUE fname; long i, j; if (f[0] == '~') { fname = rb_file_expand_path(*filep, Qnil); if (rb_safe_level() >= 2 && OBJ_TAINTED(fname)) { rb_raise(rb_eSecurityError, "loading from unsafe file %s", f); } OBJ_FREEZE(fname); f = StringValueCStr(fname); *filep = fname; } if (is_absolute_path(f)) { for (i=0; ext[i]; i++) { fname = rb_str_dup(*filep); rb_str_cat2(fname, ext[i]); OBJ_FREEZE(fname); if (file_load_ok(StringValueCStr(fname))) { *filep = fname; return i+1; } } return 0; } if (!rb_load_path) return 0; Check_Type(rb_load_path, T_ARRAY); for (i=0;ilen;i++) { VALUE str = RARRAY(rb_load_path)->ptr[i]; SafeStringValue(str); if (RSTRING(str)->len == 0) continue; path = RSTRING(str)->ptr; for (j=0; ext[j]; j++) { fname = rb_str_dup(*filep); rb_str_cat2(fname, ext[j]); OBJ_FREEZE(fname); found = dln_find_file(StringValueCStr(fname), path); if (found && file_load_ok(found)) { *filep = fname; return j+1; } } } return 0; } VALUE rb_find_file(path) VALUE path; { VALUE tmp; char *f = StringValueCStr(path); char *lpath; if (f[0] == '~') { path = rb_file_expand_path(path, Qnil); if (rb_safe_level() >= 1 && OBJ_TAINTED(path)) { rb_raise(rb_eSecurityError, "loading from unsafe path %s", f); } OBJ_FREEZE(path); f = StringValueCStr(path); } #if defined(__MACOS__) || defined(riscos) if (is_macos_native_path(f)) { if (rb_safe_level() >= 1 && !fpath_check(f)) { rb_raise(rb_eSecurityError, "loading from unsafe file %s", f); } if (file_load_ok(f)) return path; } #endif if (is_absolute_path(f)) { if (rb_safe_level() >= 1 && !fpath_check(f)) { rb_raise(rb_eSecurityError, "loading from unsafe file %s", f); } if (file_load_ok(f)) return path; } if (rb_safe_level() >= 4) { rb_raise(rb_eSecurityError, "loading from non-absolute path %s", f); } if (rb_load_path) { long i; Check_Type(rb_load_path, T_ARRAY); tmp = rb_ary_new(); for (i=0;ilen;i++) { VALUE str = RARRAY(rb_load_path)->ptr[i]; SafeStringValue(str); if (RSTRING(str)->len > 0) { rb_ary_push(tmp, str); } } tmp = rb_ary_join(tmp, rb_str_new2(PATH_SEP)); if (RSTRING(tmp)->len == 0) { lpath = 0; } else { lpath = RSTRING(tmp)->ptr; } } else { lpath = 0; } if (!lpath) { return 0; /* no path, no load */ } if (!(f = dln_find_file(f, lpath))) { return 0; } if (rb_safe_level() >= 1 && !fpath_check(f)) { rb_raise(rb_eSecurityError, "loading from unsafe file %s", f); } if (file_load_ok(f)) { tmp = rb_str_new2(f); OBJ_FREEZE(tmp); return tmp; } return 0; } static void define_filetest_function(name, func, argc) const char *name; VALUE (*func)(); int argc; { rb_define_module_function(rb_mFileTest, name, func, argc); rb_define_singleton_method(rb_cFile, name, func, argc); } /* * A File is an abstraction of any file object accessible * by the program and is closely associated with class IO * File includes the methods of module * FileTest as class methods, allowing you to write (for * example) File.exist?("foo"). * * In the description of File methods, * permission bits are a platform-specific * set of bits that indicate permissions of a file. On Unix-based * systems, permissions are viewed as a set of three octets, for the * owner, the group, and the rest of the world. For each of these * entities, permissions may be set to read, write, or execute the * file: * * The permission bits 0644 (in octal) would thus be * interpreted as read/write for owner, and read-only for group and * other. Higher-order bits may also be used to indicate the type of * file (plain, directory, pipe, socket, and so on) and various other * special features. If the permissions are for a directory, the * meaning of the execute bit changes; when set the directory can be * searched. * * On non-Posix operating systems, there may be only the ability to * make a file read-only or read-write. In this case, the remaining * permission bits will be synthesized to resemble typical values. For * instance, on Windows NT the default permission bits are * 0644, which means read/write for owner, read-only for * all others. The only change that can be made is to make the file * read-only, which is reported as 0444. */ void Init_File() { rb_mFileTest = rb_define_module("FileTest"); rb_cFile = rb_define_class("File", rb_cIO); define_filetest_function("directory?", test_d, 1); define_filetest_function("exist?", test_e, 1); define_filetest_function("exists?", test_e, 1); /* temporary */ define_filetest_function("readable?", test_r, 1); define_filetest_function("readable_real?", test_R, 1); define_filetest_function("writable?", test_w, 1); define_filetest_function("writable_real?", test_W, 1); define_filetest_function("executable?", test_x, 1); define_filetest_function("executable_real?", test_X, 1); define_filetest_function("file?", test_f, 1); define_filetest_function("zero?", test_z, 1); define_filetest_function("size?", test_s, 1); define_filetest_function("size", rb_file_s_size, 1); define_filetest_function("owned?", test_owned, 1); define_filetest_function("grpowned?", test_grpowned, 1); define_filetest_function("pipe?", test_p, 1); define_filetest_function("symlink?", test_l, 1); define_filetest_function("socket?", test_S, 1); define_filetest_function("blockdev?", test_b, 1); define_filetest_function("chardev?", test_c, 1); define_filetest_function("setuid?", test_suid, 1); define_filetest_function("setgid?", test_sgid, 1); define_filetest_function("sticky?", test_sticky, 1); define_filetest_function("identical?", test_identical, 2); rb_define_singleton_method(rb_cFile, "stat", rb_file_s_stat, 1); rb_define_singleton_method(rb_cFile, "lstat", rb_file_s_lstat, 1); rb_define_singleton_method(rb_cFile, "ftype", rb_file_s_ftype, 1); rb_define_singleton_method(rb_cFile, "atime", rb_file_s_atime, 1); rb_define_singleton_method(rb_cFile, "mtime", rb_file_s_mtime, 1); rb_define_singleton_method(rb_cFile, "ctime", rb_file_s_ctime, 1); rb_define_singleton_method(rb_cFile, "utime", rb_file_s_utime, -1); rb_define_singleton_method(rb_cFile, "chmod", rb_file_s_chmod, -1); rb_define_singleton_method(rb_cFile, "chown", rb_file_s_chown, -1); rb_define_singleton_method(rb_cFile, "lchmod", rb_file_s_lchmod, -1); rb_define_singleton_method(rb_cFile, "lchown", rb_file_s_lchown, -1); rb_define_singleton_method(rb_cFile, "link", rb_file_s_link, 2); rb_define_singleton_method(rb_cFile, "symlink", rb_file_s_symlink, 2); rb_define_singleton_method(rb_cFile, "readlink", rb_file_s_readlink, 1); rb_define_singleton_method(rb_cFile, "unlink", rb_file_s_unlink, -2); rb_define_singleton_method(rb_cFile, "delete", rb_file_s_unlink, -2); rb_define_singleton_method(rb_cFile, "rename", rb_file_s_rename, 2); rb_define_singleton_method(rb_cFile, "umask", rb_file_s_umask, -1); rb_define_singleton_method(rb_cFile, "truncate", rb_file_s_truncate, 2); rb_define_singleton_method(rb_cFile, "expand_path", rb_file_s_expand_path, -1); rb_define_singleton_method(rb_cFile, "basename", rb_file_s_basename, -1); rb_define_singleton_method(rb_cFile, "dirname", rb_file_s_dirname, 1); rb_define_singleton_method(rb_cFile, "extname", rb_file_s_extname, 1); separator = rb_obj_freeze(rb_str_new2("/")); rb_define_const(rb_cFile, "Separator", separator); rb_define_const(rb_cFile, "SEPARATOR", separator); rb_define_singleton_method(rb_cFile, "split", rb_file_s_split, 1); rb_define_singleton_method(rb_cFile, "join", rb_file_s_join, -2); #ifdef DOSISH rb_define_const(rb_cFile, "ALT_SEPARATOR", rb_obj_freeze(rb_str_new2("\\"))); #else rb_define_const(rb_cFile, "ALT_SEPARATOR", Qnil); #endif rb_define_const(rb_cFile, "PATH_SEPARATOR", rb_obj_freeze(rb_str_new2(PATH_SEP))); rb_define_method(rb_cIO, "stat", rb_io_stat, 0); /* this is IO's method */ rb_define_method(rb_cFile, "lstat", rb_file_lstat, 0); rb_define_method(rb_cFile, "atime", rb_file_atime, 0); rb_define_method(rb_cFile, "mtime", rb_file_mtime, 0); rb_define_method(rb_cFile, "ctime", rb_file_ctime, 0); rb_define_method(rb_cFile, "chmod", rb_file_chmod, 1); rb_define_method(rb_cFile, "chown", rb_file_chown, 2); rb_define_method(rb_cFile, "truncate", rb_file_truncate, 1); rb_define_method(rb_cFile, "flock", rb_file_flock, 1); rb_mFConst = rb_define_module_under(rb_cFile, "Constants"); rb_include_module(rb_cIO, rb_mFConst); rb_file_const("LOCK_SH", INT2FIX(LOCK_SH)); rb_file_const("LOCK_EX", INT2FIX(LOCK_EX)); rb_file_const("LOCK_UN", INT2FIX(LOCK_UN)); rb_file_const("LOCK_NB", INT2FIX(LOCK_NB)); rb_define_method(rb_cFile, "path", rb_file_path, 0); rb_define_global_function("test", rb_f_test, -1); rb_cStat = rb_define_class_under(rb_cFile, "Stat", rb_cObject); rb_define_alloc_func(rb_cStat, rb_stat_s_alloc); rb_define_method(rb_cStat, "initialize", rb_stat_init, 1); rb_define_method(rb_cStat, "initialize_copy", rb_stat_init_copy, 1); rb_include_module(rb_cStat, rb_mComparable); rb_define_method(rb_cStat, "<=>", rb_stat_cmp, 1); rb_define_method(rb_cStat, "dev", rb_stat_dev, 0); rb_define_method(rb_cStat, "dev_major", rb_stat_dev_major, 0); rb_define_method(rb_cStat, "dev_minor", rb_stat_dev_minor, 0); rb_define_method(rb_cStat, "ino", rb_stat_ino, 0); rb_define_method(rb_cStat, "mode", rb_stat_mode, 0); rb_define_method(rb_cStat, "nlink", rb_stat_nlink, 0); rb_define_method(rb_cStat, "uid", rb_stat_uid, 0); rb_define_method(rb_cStat, "gid", rb_stat_gid, 0); rb_define_method(rb_cStat, "rdev", rb_stat_rdev, 0); rb_define_method(rb_cStat, "rdev_major", rb_stat_rdev_major, 0); rb_define_method(rb_cStat, "rdev_minor", rb_stat_rdev_minor, 0); rb_define_method(rb_cStat, "size", rb_stat_size, 0); rb_define_method(rb_cStat, "blksize", rb_stat_blksize, 0); rb_define_method(rb_cStat, "blocks", rb_stat_blocks, 0); rb_define_method(rb_cStat, "atime", rb_stat_atime, 0); rb_define_method(rb_cStat, "mtime", rb_stat_mtime, 0); rb_define_method(rb_cStat, "ctime", rb_stat_ctime, 0); rb_define_method(rb_cStat, "inspect", rb_stat_inspect, 0); rb_define_method(rb_cStat, "ftype", rb_stat_ftype, 0); rb_define_method(rb_cStat, "directory?", rb_stat_d, 0); rb_define_method(rb_cStat, "readable?", rb_stat_r, 0); rb_define_method(rb_cStat, "readable_real?", rb_stat_R, 0); rb_define_method(rb_cStat, "writable?", rb_stat_w, 0); rb_define_method(rb_cStat, "writable_real?", rb_stat_W, 0); rb_define_method(rb_cStat, "executable?", rb_stat_x, 0); rb_define_method(rb_cStat, "executable_real?", rb_stat_X, 0); rb_define_method(rb_cStat, "file?", rb_stat_f, 0); rb_define_method(rb_cStat, "zero?", rb_stat_z, 0); rb_define_method(rb_cStat, "size?", rb_stat_s, 0); rb_define_method(rb_cStat, "owned?", rb_stat_owned, 0); rb_define_method(rb_cStat, "grpowned?", rb_stat_grpowned, 0); rb_define_method(rb_cStat, "pipe?", rb_stat_p, 0); rb_define_method(rb_cStat, "symlink?", rb_stat_l, 0); rb_define_method(rb_cStat, "socket?", rb_stat_S, 0); rb_define_method(rb_cStat, "blockdev?", rb_stat_b, 0); rb_define_method(rb_cStat, "chardev?", rb_stat_c, 0); rb_define_method(rb_cStat, "setuid?", rb_stat_suid, 0); rb_define_method(rb_cStat, "setgid?", rb_stat_sgid, 0); rb_define_method(rb_cStat, "sticky?", rb_stat_sticky, 0); } /********************************************************************** gc.c - $Author: wyhaines $ $Date: 2009-07-10 20:31:58 +0200 (Fri, 10 Jul 2009) $ created at: Tue Oct 5 09:44:46 JST 1993 Copyright (C) 1993-2003 Yukihiro Matsumoto Copyright (C) 2000 Network Applied Communication Laboratory, Inc. Copyright (C) 2000 Information-technology Promotion Agency, Japan **********************************************************************/ #include "ruby.h" #include "rubysig.h" #include "st.h" #include "node.h" #include "env.h" #include "re.h" #include #include #include #ifdef HAVE_SYS_TIME_H #include #endif #ifdef HAVE_SYS_RESOURCE_H #include #endif #if defined _WIN32 || defined __CYGWIN__ #include #endif void re_free_registers _((struct re_registers*)); void rb_io_fptr_finalize _((struct OpenFile*)); #if !defined(setjmp) && defined(HAVE__SETJMP) #define setjmp(env) _setjmp(env) #endif /* Make alloca work the best possible way. */ #ifdef __GNUC__ # ifndef atarist # ifndef alloca # define alloca __builtin_alloca # endif # endif /* atarist */ #else # ifdef HAVE_ALLOCA_H # include # else # ifndef _AIX # ifndef alloca /* predefined by HP cc +Olibcalls */ void *alloca (); # endif # endif /* AIX */ # endif /* HAVE_ALLOCA_H */ #endif /* __GNUC__ */ #ifndef GC_MALLOC_LIMIT #if defined(MSDOS) || defined(__human68k__) #define GC_MALLOC_LIMIT 200000 #else #define GC_MALLOC_LIMIT 8000000 #endif #endif static unsigned long malloc_increase = 0; static unsigned long malloc_limit = GC_MALLOC_LIMIT; static void run_final(); static VALUE nomem_error; static void garbage_collect(); NORETURN(void rb_exc_jump _((VALUE))); void rb_memerror() { rb_thread_t th = rb_curr_thread; if (!nomem_error || (rb_thread_raised_p(th, RAISED_NOMEMORY) && rb_safe_level() < 4)) { fprintf(stderr, "[FATAL] failed to allocate memory\n"); exit(1); } if (rb_thread_raised_p(th, RAISED_NOMEMORY)) { rb_exc_jump(nomem_error); } rb_thread_raised_set(th, RAISED_NOMEMORY); rb_exc_raise(nomem_error); } void * ruby_xmalloc(size) long size; { void *mem; if (size < 0) { rb_raise(rb_eNoMemError, "negative allocation size (or too big)"); } if (size == 0) size = 1; if ((malloc_increase+size) > malloc_limit) { garbage_collect(); } RUBY_CRITICAL(mem = malloc(size)); if (!mem) { garbage_collect(); RUBY_CRITICAL(mem = malloc(size)); if (!mem) { rb_memerror(); } } malloc_increase += size; return mem; } void * ruby_xcalloc(n, size) long n, size; { void *mem; mem = xmalloc(n * size); memset(mem, 0, n * size); return mem; } void * ruby_xrealloc(ptr, size) void *ptr; long size; { void *mem; if (size < 0) { rb_raise(rb_eArgError, "negative re-allocation size"); } if (!ptr) return xmalloc(size); if (size == 0) size = 1; RUBY_CRITICAL(mem = realloc(ptr, size)); if (!mem) { garbage_collect(); RUBY_CRITICAL(mem = realloc(ptr, size)); if (!mem) { rb_memerror(); } } malloc_increase += size; return mem; } void ruby_xfree(x) void *x; { if (x) RUBY_CRITICAL(free(x)); } extern int ruby_in_compile; static int dont_gc; static int during_gc; static int need_call_final = 0; static st_table *finalizer_table = 0; /* * call-seq: * GC.enable => true or false * * Enables garbage collection, returning true if garbage * collection was previously disabled. * * GC.disable #=> false * GC.enable #=> true * GC.enable #=> false * */ VALUE rb_gc_enable() { int old = dont_gc; dont_gc = Qfalse; return old; } /* * call-seq: * GC.disable => true or false * * Disables garbage collection, returning true if garbage * collection was already disabled. * * GC.disable #=> false * GC.disable #=> true * */ VALUE rb_gc_disable() { int old = dont_gc; dont_gc = Qtrue; return old; } VALUE rb_mGC; static struct gc_list { VALUE *varptr; struct gc_list *next; } *global_List = 0; void rb_gc_register_address(addr) VALUE *addr; { struct gc_list *tmp; tmp = ALLOC(struct gc_list); tmp->next = global_List; tmp->varptr = addr; global_List = tmp; } void rb_gc_unregister_address(addr) VALUE *addr; { struct gc_list *tmp = global_List; if (tmp->varptr == addr) { global_List = tmp->next; RUBY_CRITICAL(free(tmp)); return; } while (tmp->next) { if (tmp->next->varptr == addr) { struct gc_list *t = tmp->next; tmp->next = tmp->next->next; RUBY_CRITICAL(free(t)); break; } tmp = tmp->next; } } #undef GC_DEBUG void rb_global_variable(var) VALUE *var; { rb_gc_register_address(var); } #if defined(_MSC_VER) || defined(__BORLANDC__) || defined(__CYGWIN__) #pragma pack(push, 1) /* magic for reducing sizeof(RVALUE): 24 -> 20 */ #endif typedef struct RVALUE { union { struct { unsigned long flags; /* always 0 for freed obj */ struct RVALUE *next; } free; struct RBasic basic; struct RObject object; struct RClass klass; struct RFloat flonum; struct RString string; struct RArray array; struct RRegexp regexp; struct RHash hash; struct RData data; struct RStruct rstruct; struct RBignum bignum; struct RFile file; struct RNode node; struct RMatch match; struct RVarmap varmap; struct SCOPE scope; } as; #ifdef GC_DEBUG char *file; int line; #endif } RVALUE; #if defined(_MSC_VER) || defined(__BORLANDC__) || defined(__CYGWIN__) #pragma pack(pop) #endif static RVALUE *freelist = 0; static RVALUE *deferred_final_list = 0; #define HEAPS_INCREMENT 10 static struct heaps_slot { void *membase; RVALUE *slot; int limit; } *heaps; static int heaps_length = 0; static int heaps_used = 0; #define HEAP_MIN_SLOTS 10000 static int heap_slots = HEAP_MIN_SLOTS; #define FREE_MIN 4096 static RVALUE *himem, *lomem; static void add_heap() { RVALUE *p, *pend; if (heaps_used == heaps_length) { /* Realloc heaps */ struct heaps_slot *p; int length; heaps_length += HEAPS_INCREMENT; length = heaps_length*sizeof(struct heaps_slot); RUBY_CRITICAL( if (heaps_used > 0) { p = (struct heaps_slot *)realloc(heaps, length); if (p) heaps = p; } else { p = heaps = (struct heaps_slot *)malloc(length); }); if (p == 0) rb_memerror(); } for (;;) { RUBY_CRITICAL(p = (RVALUE*)malloc(sizeof(RVALUE)*(heap_slots+1))); if (p == 0) { if (heap_slots == HEAP_MIN_SLOTS) { rb_memerror(); } heap_slots = HEAP_MIN_SLOTS; continue; } heaps[heaps_used].membase = p; if ((VALUE)p % sizeof(RVALUE) == 0) heap_slots += 1; else p = (RVALUE*)((VALUE)p + sizeof(RVALUE) - ((VALUE)p % sizeof(RVALUE))); heaps[heaps_used].slot = p; heaps[heaps_used].limit = heap_slots; break; } pend = p + heap_slots; if (lomem == 0 || lomem > p) lomem = p; if (himem < pend) himem = pend; heaps_used++; heap_slots *= 1.8; if (heap_slots <= 0) heap_slots = HEAP_MIN_SLOTS; while (p < pend) { p->as.free.flags = 0; p->as.free.next = freelist; freelist = p; p++; } } #define RANY(o) ((RVALUE*)(o)) int rb_during_gc() { return during_gc; } VALUE rb_newobj() { VALUE obj; if (during_gc) rb_bug("object allocation during garbage collection phase"); if (!freelist) garbage_collect(); obj = (VALUE)freelist; freelist = freelist->as.free.next; MEMZERO((void*)obj, RVALUE, 1); #ifdef GC_DEBUG RANY(obj)->file = ruby_sourcefile; RANY(obj)->line = ruby_sourceline; #endif return obj; } VALUE rb_data_object_alloc(klass, datap, dmark, dfree) VALUE klass; void *datap; RUBY_DATA_FUNC dmark; RUBY_DATA_FUNC dfree; { NEWOBJ(data, struct RData); if (klass) Check_Type(klass, T_CLASS); OBJSETUP(data, klass, T_DATA); data->data = datap; data->dfree = dfree; data->dmark = dmark; return (VALUE)data; } extern st_table *rb_class_tbl; VALUE *rb_gc_stack_start = 0; #ifdef __ia64 VALUE *rb_gc_register_stack_start = 0; #endif #ifdef DJGPP /* set stack size (http://www.delorie.com/djgpp/v2faq/faq15_9.html) */ unsigned int _stklen = 0x180000; /* 1.5 kB */ #endif #if defined(DJGPP) || defined(_WIN32_WCE) static unsigned int STACK_LEVEL_MAX = 65535; #elif defined(__human68k__) unsigned int _stacksize = 262144; # define STACK_LEVEL_MAX (_stacksize - 4096) # undef HAVE_GETRLIMIT #elif defined(HAVE_GETRLIMIT) || defined(_WIN32) static size_t STACK_LEVEL_MAX = 655300; #else # define STACK_LEVEL_MAX 655300 #endif #ifdef C_ALLOCA # define SET_STACK_END VALUE stack_end; alloca(0); # define STACK_END (&stack_end) #else # if defined(__GNUC__) && defined(USE_BUILTIN_FRAME_ADDRESS) && !defined(__ia64) # if ( __GNUC__ == 3 && __GNUC_MINOR__ > 0 ) || __GNUC__ > 3 __attribute__ ((noinline)) # endif static void stack_end_address(VALUE **stack_end_p) { VALUE stack_end; *stack_end_p = &stack_end; } # define SET_STACK_END VALUE *stack_end; stack_end_address(&stack_end) # else # define SET_STACK_END VALUE *stack_end = alloca(1) # endif # define STACK_END (stack_end) #endif #if STACK_GROW_DIRECTION < 0 # define STACK_LENGTH (rb_gc_stack_start - STACK_END) #elif STACK_GROW_DIRECTION > 0 # define STACK_LENGTH (STACK_END - rb_gc_stack_start + 1) #else # define STACK_LENGTH ((STACK_END < rb_gc_stack_start) ? rb_gc_stack_start - STACK_END\ : STACK_END - rb_gc_stack_start + 1) #endif #if STACK_GROW_DIRECTION > 0 # define STACK_UPPER(x, a, b) a #elif STACK_GROW_DIRECTION < 0 # define STACK_UPPER(x, a, b) b #else static int grow_direction; static int stack_grow_direction(addr) VALUE *addr; { SET_STACK_END; if (STACK_END > addr) return grow_direction = 1; return grow_direction = -1; } # define stack_growup_p(x) ((grow_direction ? grow_direction : stack_grow_direction(x)) > 0) # define STACK_UPPER(x, a, b) (stack_growup_p(x) ? a : b) #endif #define GC_WATER_MARK 512 #define CHECK_STACK(ret) do {\ SET_STACK_END;\ (ret) = (STACK_LENGTH > STACK_LEVEL_MAX + GC_WATER_MARK);\ } while (0) size_t ruby_stack_length(p) VALUE **p; { SET_STACK_END; if (p) *p = STACK_UPPER(STACK_END, rb_gc_stack_start, STACK_END); return STACK_LENGTH; } int ruby_stack_check() { int ret; CHECK_STACK(ret); return ret; } #define MARK_STACK_MAX 1024 static VALUE mark_stack[MARK_STACK_MAX]; static VALUE *mark_stack_ptr; static int mark_stack_overflow; static void init_mark_stack() { mark_stack_overflow = 0; mark_stack_ptr = mark_stack; } #define MARK_STACK_EMPTY (mark_stack_ptr == mark_stack) static st_table *source_filenames; char * rb_source_filename(f) const char *f; { st_data_t name; if (!st_lookup(source_filenames, (st_data_t)f, &name)) { long len = strlen(f) + 1; char *ptr = ALLOC_N(char, len + 1); name = (st_data_t)ptr; *ptr++ = 0; MEMCPY(ptr, f, char, len); st_add_direct(source_filenames, (st_data_t)ptr, name); return ptr; } return (char *)name + 1; } static void mark_source_filename(f) char *f; { if (f) { f[-1] = 1; } } static int sweep_source_filename(key, value) char *key, *value; { if (*value) { *value = 0; return ST_CONTINUE; } else { free(value); return ST_DELETE; } } static void gc_mark _((VALUE ptr, int lev)); static void gc_mark_children _((VALUE ptr, int lev)); static void gc_mark_all() { RVALUE *p, *pend; int i; init_mark_stack(); for (i = 0; i < heaps_used; i++) { p = heaps[i].slot; pend = p + heaps[i].limit; while (p < pend) { if ((p->as.basic.flags & FL_MARK) && (p->as.basic.flags != FL_MARK)) { gc_mark_children((VALUE)p, 0); } p++; } } } static void gc_mark_rest() { VALUE tmp_arry[MARK_STACK_MAX]; VALUE *p; p = (mark_stack_ptr - mark_stack) + tmp_arry; MEMCPY(tmp_arry, mark_stack, VALUE, MARK_STACK_MAX); init_mark_stack(); while(p != tmp_arry){ p--; gc_mark_children(*p, 0); } } static inline int is_pointer_to_heap(ptr) void *ptr; { register RVALUE *p = RANY(ptr); register RVALUE *heap_org; register long i; if (p < lomem || p > himem) return Qfalse; if ((VALUE)p % sizeof(RVALUE) != 0) return Qfalse; /* check if p looks like a pointer */ for (i=0; i < heaps_used; i++) { heap_org = heaps[i].slot; if (heap_org <= p && p < heap_org + heaps[i].limit) return Qtrue; } return Qfalse; } static void mark_locations_array(x, n) register VALUE *x; register long n; { VALUE v; while (n--) { v = *x; if (is_pointer_to_heap((void *)v)) { gc_mark(v, 0); } x++; } } void rb_gc_mark_locations(start, end) VALUE *start, *end; { long n; n = end - start; mark_locations_array(start,n); } static int mark_entry(key, value, lev) ID key; VALUE value; int lev; { gc_mark(value, lev); return ST_CONTINUE; } static void mark_tbl(tbl, lev) st_table *tbl; int lev; { if (!tbl) return; st_foreach(tbl, mark_entry, lev); } void rb_mark_tbl(tbl) st_table *tbl; { mark_tbl(tbl, 0); } static int mark_key(key, value, lev) VALUE key, value; int lev; { gc_mark(key, lev); return ST_CONTINUE; } static void mark_set(tbl, lev) st_table *tbl; int lev; { if (!tbl) return; st_foreach(tbl, mark_key, lev); } void rb_mark_set(tbl) st_table *tbl; { mark_set(tbl, 0); } static int mark_keyvalue(key, value, lev) VALUE key; VALUE value; int lev; { gc_mark(key, lev); gc_mark(value, lev); return ST_CONTINUE; } static void mark_hash(tbl, lev) st_table *tbl; int lev; { if (!tbl) return; st_foreach(tbl, mark_keyvalue, lev); } void rb_mark_hash(tbl) st_table *tbl; { mark_hash(tbl, 0); } void rb_gc_mark_maybe(obj) VALUE obj; { if (is_pointer_to_heap((void *)obj)) { gc_mark(obj, 0); } } #define GC_LEVEL_MAX 250 static void gc_mark(ptr, lev) VALUE ptr; int lev; { register RVALUE *obj; obj = RANY(ptr); if (rb_special_const_p(ptr)) return; /* special const not marked */ if (obj->as.basic.flags == 0) return; /* free cell */ if (obj->as.basic.flags & FL_MARK) return; /* already marked */ obj->as.basic.flags |= FL_MARK; if (lev > GC_LEVEL_MAX || (lev == 0 && ruby_stack_check())) { if (!mark_stack_overflow) { if (mark_stack_ptr - mark_stack < MARK_STACK_MAX) { *mark_stack_ptr = ptr; mark_stack_ptr++; } else { mark_stack_overflow = 1; } } return; } gc_mark_children(ptr, lev+1); } void rb_gc_mark(ptr) VALUE ptr; { gc_mark(ptr, 0); } static void gc_mark_children(ptr, lev) VALUE ptr; int lev; { register RVALUE *obj = RANY(ptr); goto marking; /* skip */ again: obj = RANY(ptr); if (rb_special_const_p(ptr)) return; /* special const not marked */ if (obj->as.basic.flags == 0) return; /* free cell */ if (obj->as.basic.flags & FL_MARK) return; /* already marked */ obj->as.basic.flags |= FL_MARK; marking: if (FL_TEST(obj, FL_EXIVAR)) { rb_mark_generic_ivar(ptr); } switch (obj->as.basic.flags & T_MASK) { case T_NIL: case T_FIXNUM: rb_bug("rb_gc_mark() called for broken object"); break; case T_NODE: mark_source_filename(obj->as.node.nd_file); switch (nd_type(obj)) { case NODE_IF: /* 1,2,3 */ case NODE_FOR: case NODE_ITER: case NODE_CREF: case NODE_WHEN: case NODE_MASGN: case NODE_RESCUE: case NODE_RESBODY: case NODE_CLASS: gc_mark((VALUE)obj->as.node.u2.node, lev); /* fall through */ case NODE_BLOCK: /* 1,3 */ case NODE_ARRAY: case NODE_DSTR: case NODE_DXSTR: case NODE_DREGX: case NODE_DREGX_ONCE: case NODE_FBODY: case NODE_ENSURE: case NODE_CALL: case NODE_DEFS: case NODE_OP_ASGN1: gc_mark((VALUE)obj->as.node.u1.node, lev); /* fall through */ case NODE_SUPER: /* 3 */ case NODE_FCALL: case NODE_DEFN: case NODE_NEWLINE: ptr = (VALUE)obj->as.node.u3.node; goto again; case NODE_WHILE: /* 1,2 */ case NODE_UNTIL: case NODE_AND: case NODE_OR: case NODE_CASE: case NODE_SCLASS: case NODE_DOT2: case NODE_DOT3: case NODE_FLIP2: case NODE_FLIP3: case NODE_MATCH2: case NODE_MATCH3: case NODE_OP_ASGN_OR: case NODE_OP_ASGN_AND: case NODE_MODULE: case NODE_ALIAS: case NODE_VALIAS: case NODE_ARGS: gc_mark((VALUE)obj->as.node.u1.node, lev); /* fall through */ case NODE_METHOD: /* 2 */ case NODE_NOT: case NODE_GASGN: case NODE_LASGN: case NODE_DASGN: case NODE_DASGN_CURR: case NODE_IASGN: case NODE_CVDECL: case NODE_CVASGN: case NODE_COLON3: case NODE_OPT_N: case NODE_EVSTR: case NODE_UNDEF: ptr = (VALUE)obj->as.node.u2.node; goto again; case NODE_HASH: /* 1 */ case NODE_LIT: case NODE_STR: case NODE_XSTR: case NODE_DEFINED: case NODE_MATCH: case NODE_RETURN: case NODE_BREAK: case NODE_NEXT: case NODE_YIELD: case NODE_COLON2: case NODE_SPLAT: case NODE_TO_ARY: case NODE_SVALUE: ptr = (VALUE)obj->as.node.u1.node; goto again; case NODE_SCOPE: /* 2,3 */ case NODE_BLOCK_PASS: case NODE_CDECL: gc_mark((VALUE)obj->as.node.u3.node, lev); ptr = (VALUE)obj->as.node.u2.node; goto again; case NODE_ZARRAY: /* - */ case NODE_ZSUPER: case NODE_CFUNC: case NODE_VCALL: case NODE_GVAR: case NODE_LVAR: case NODE_DVAR: case NODE_IVAR: case NODE_CVAR: case NODE_NTH_REF: case NODE_BACK_REF: case NODE_REDO: case NODE_RETRY: case NODE_SELF: case NODE_NIL: case NODE_TRUE: case NODE_FALSE: case NODE_ATTRSET: case NODE_BLOCK_ARG: case NODE_POSTEXE: break; case NODE_ALLOCA: mark_locations_array((VALUE*)obj->as.node.u1.value, obj->as.node.u3.cnt); ptr = (VALUE)obj->as.node.u2.node; goto again; default: /* unlisted NODE */ if (is_pointer_to_heap(obj->as.node.u1.node)) { gc_mark((VALUE)obj->as.node.u1.node, lev); } if (is_pointer_to_heap(obj->as.node.u2.node)) { gc_mark((VALUE)obj->as.node.u2.node, lev); } if (is_pointer_to_heap(obj->as.node.u3.node)) { gc_mark((VALUE)obj->as.node.u3.node, lev); } } return; /* no need to mark class. */ } gc_mark(obj->as.basic.klass, lev); switch (obj->as.basic.flags & T_MASK) { case T_ICLASS: case T_CLASS: case T_MODULE: mark_tbl(obj->as.klass.m_tbl, lev); mark_tbl(obj->as.klass.iv_tbl, lev); ptr = obj->as.klass.super; goto again; case T_ARRAY: if (FL_TEST(obj, ELTS_SHARED)) { ptr = obj->as.array.aux.shared; goto again; } else { long i, len = obj->as.array.len; VALUE *ptr = obj->as.array.ptr; for (i=0; i < len; i++) { gc_mark(*ptr++, lev); } } break; case T_HASH: mark_hash(obj->as.hash.tbl, lev); ptr = obj->as.hash.ifnone; goto again; case T_STRING: #define STR_ASSOC FL_USER3 /* copied from string.c */ if (FL_TEST(obj, ELTS_SHARED|STR_ASSOC)) { ptr = obj->as.string.aux.shared; goto again; } break; case T_DATA: if (obj->as.data.dmark) (*obj->as.data.dmark)(DATA_PTR(obj)); break; case T_OBJECT: mark_tbl(obj->as.object.iv_tbl, lev); break; case T_FILE: case T_REGEXP: case T_FLOAT: case T_BIGNUM: case T_BLKTAG: break; case T_MATCH: if (obj->as.match.str) { ptr = obj->as.match.str; goto again; } break; case T_VARMAP: gc_mark(obj->as.varmap.val, lev); ptr = (VALUE)obj->as.varmap.next; goto again; case T_SCOPE: if (obj->as.scope.local_vars && (obj->as.scope.flags & SCOPE_MALLOC)) { int n = obj->as.scope.local_tbl[0]+1; VALUE *vars = &obj->as.scope.local_vars[-1]; while (n--) { gc_mark(*vars++, lev); } } break; case T_STRUCT: { long len = obj->as.rstruct.len; VALUE *ptr = obj->as.rstruct.ptr; while (len--) { gc_mark(*ptr++, lev); } } break; default: rb_bug("rb_gc_mark(): unknown data type 0x%lx(0x%lx) %s", obj->as.basic.flags & T_MASK, obj, is_pointer_to_heap(obj) ? "corrupted object" : "non object"); } } static int obj_free _((VALUE)); static inline void add_freelist(p) RVALUE *p; { p->as.free.flags = 0; p->as.free.next = freelist; freelist = p; } static void finalize_list(p) RVALUE *p; { while (p) { RVALUE *tmp = p->as.free.next; run_final((VALUE)p); if (!FL_TEST(p, FL_SINGLETON)) { /* not freeing page */ add_freelist(p); } p = tmp; } } static void free_unused_heaps() { int i, j; for (i = j = 1; j < heaps_used; i++) { if (heaps[i].limit == 0) { free(heaps[i].membase); heaps_used--; } else { if (i != j) { heaps[j] = heaps[i]; } j++; } } } #define T_DEFERRED 0x3a void rb_gc_abort_threads(void); static void gc_sweep() { RVALUE *p, *pend, *final_list; int freed = 0; int i; unsigned long live = 0; unsigned long free_min = 0; for (i = 0; i < heaps_used; i++) { free_min += heaps[i].limit; } free_min = free_min * 0.2; if (free_min < FREE_MIN) free_min = FREE_MIN; if (ruby_in_compile && ruby_parser_stack_on_heap()) { /* should not reclaim nodes during compilation if yacc's semantic stack is not allocated on machine stack */ for (i = 0; i < heaps_used; i++) { p = heaps[i].slot; pend = p + heaps[i].limit; while (p < pend) { if (!(p->as.basic.flags&FL_MARK) && BUILTIN_TYPE(p) == T_NODE) gc_mark((VALUE)p, 0); p++; } } } mark_source_filename(ruby_sourcefile); if (source_filenames) { st_foreach(source_filenames, sweep_source_filename, 0); } freelist = 0; final_list = deferred_final_list; deferred_final_list = 0; for (i = 0; i < heaps_used; i++) { int n = 0; RVALUE *free = freelist; RVALUE *final = final_list; int deferred; p = heaps[i].slot; pend = p + heaps[i].limit; while (p < pend) { if (!(p->as.basic.flags & FL_MARK)) { if (p->as.basic.flags && ((deferred = obj_free((VALUE)p)) || ((FL_TEST(p, FL_FINALIZE)) && need_call_final))) { if (!deferred) { p->as.free.flags = T_DEFERRED; RDATA(p)->dfree = 0; } p->as.free.flags |= FL_MARK; p->as.free.next = final_list; final_list = p; } else { add_freelist(p); } n++; } else if (BUILTIN_TYPE(p) == T_DEFERRED) { /* objects to be finalized */ /* do nothing remain marked */ } else { RBASIC(p)->flags &= ~FL_MARK; live++; } p++; } if (n == heaps[i].limit && freed > free_min) { RVALUE *pp; heaps[i].limit = 0; for (pp = final_list; pp != final; pp = pp->as.free.next) { pp->as.free.flags |= FL_SINGLETON; /* freeing page mark */ } freelist = free; /* cancel this page from freelist */ } else { freed += n; } } if (malloc_increase > malloc_limit) { malloc_limit += (malloc_increase - malloc_limit) * (double)live / (live + freed); if (malloc_limit < GC_MALLOC_LIMIT) malloc_limit = GC_MALLOC_LIMIT; } malloc_increase = 0; if (freed < free_min) { add_heap(); } during_gc = 0; /* clear finalization list */ if (final_list) { deferred_final_list = final_list; rb_thread_pending = 1; return; } free_unused_heaps(); } void rb_gc_force_recycle(p) VALUE p; { add_freelist((RVALUE*)p); } static inline void make_deferred(p) RVALUE *p; { p->as.basic.flags = (p->as.basic.flags & ~T_MASK) | T_DEFERRED; } static int obj_free(obj) VALUE obj; { switch (BUILTIN_TYPE(obj)) { case T_NIL: case T_FIXNUM: case T_TRUE: case T_FALSE: rb_bug("obj_free() called for broken object"); break; } if (FL_TEST(obj, FL_EXIVAR)) { rb_free_generic_ivar((VALUE)obj); } switch (BUILTIN_TYPE(obj)) { case T_OBJECT: if (RANY(obj)->as.object.iv_tbl) { st_free_table(RANY(obj)->as.object.iv_tbl); } break; case T_MODULE: case T_CLASS: rb_clear_cache_by_class((VALUE)obj); st_free_table(RANY(obj)->as.klass.m_tbl); if (RANY(obj)->as.object.iv_tbl) { st_free_table(RANY(obj)->as.object.iv_tbl); } break; case T_STRING: if (RANY(obj)->as.string.ptr && !FL_TEST(obj, ELTS_SHARED)) { RUBY_CRITICAL(free(RANY(obj)->as.string.ptr)); } break; case T_ARRAY: if (RANY(obj)->as.array.ptr && !FL_TEST(obj, ELTS_SHARED)) { RUBY_CRITICAL(free(RANY(obj)->as.array.ptr)); } break; case T_HASH: if (RANY(obj)->as.hash.tbl) { st_free_table(RANY(obj)->as.hash.tbl); } break; case T_REGEXP: if (RANY(obj)->as.regexp.ptr) { re_free_pattern(RANY(obj)->as.regexp.ptr); } if (RANY(obj)->as.regexp.str) { RUBY_CRITICAL(free(RANY(obj)->as.regexp.str)); } break; case T_DATA: if (DATA_PTR(obj)) { if ((long)RANY(obj)->as.data.dfree == -1) { RUBY_CRITICAL(free(DATA_PTR(obj))); } else if (RANY(obj)->as.data.dfree) { make_deferred(RANY(obj)); return 1; } } break; case T_MATCH: if (RANY(obj)->as.match.regs) { re_free_registers(RANY(obj)->as.match.regs); RUBY_CRITICAL(free(RANY(obj)->as.match.regs)); } break; case T_FILE: if (RANY(obj)->as.file.fptr) { struct rb_io_t *fptr = RANY(obj)->as.file.fptr; make_deferred(RANY(obj)); RDATA(obj)->dfree = (void (*)(void*))rb_io_fptr_finalize; RDATA(obj)->data = fptr; return 1; } break; case T_ICLASS: /* iClass shares table with the module */ break; case T_FLOAT: case T_VARMAP: case T_BLKTAG: break; case T_BIGNUM: if (RANY(obj)->as.bignum.digits) { RUBY_CRITICAL(free(RANY(obj)->as.bignum.digits)); } break; case T_NODE: switch (nd_type(obj)) { case NODE_SCOPE: if (RANY(obj)->as.node.u1.tbl) { RUBY_CRITICAL(free(RANY(obj)->as.node.u1.tbl)); } break; case NODE_ALLOCA: RUBY_CRITICAL(free(RANY(obj)->as.node.u1.node)); break; } break; /* no need to free iv_tbl */ case T_SCOPE: if (RANY(obj)->as.scope.local_vars && RANY(obj)->as.scope.flags != SCOPE_ALLOCA) { VALUE *vars = RANY(obj)->as.scope.local_vars-1; if (!(RANY(obj)->as.scope.flags & SCOPE_CLONE) && vars[0] == 0) RUBY_CRITICAL(free(RANY(obj)->as.scope.local_tbl)); if ((RANY(obj)->as.scope.flags & (SCOPE_MALLOC|SCOPE_CLONE)) == SCOPE_MALLOC) RUBY_CRITICAL(free(vars)); } break; case T_STRUCT: if (RANY(obj)->as.rstruct.ptr) { RUBY_CRITICAL(free(RANY(obj)->as.rstruct.ptr)); } break; default: rb_bug("gc_sweep(): unknown data type 0x%lx(0x%lx)", RANY(obj)->as.basic.flags & T_MASK, obj); } return 0; } void rb_gc_mark_frame(frame) struct FRAME *frame; { gc_mark((VALUE)frame->node, 0); } #ifdef __GNUC__ #if defined(__human68k__) || defined(DJGPP) #if defined(__human68k__) typedef unsigned long rb_jmp_buf[8]; __asm__ (".even\n\ _rb_setjmp:\n\ move.l 4(sp),a0\n\ movem.l d3-d7/a3-a5,(a0)\n\ moveq.l #0,d0\n\ rts"); #ifdef setjmp #undef setjmp #endif #else #if defined(DJGPP) typedef unsigned long rb_jmp_buf[6]; __asm__ (".align 4\n\ _rb_setjmp:\n\ pushl %ebp\n\ movl %esp,%ebp\n\ movl 8(%ebp),%ebp\n\ movl %eax,(%ebp)\n\ movl %ebx,4(%ebp)\n\ movl %ecx,8(%ebp)\n\ movl %edx,12(%ebp)\n\ movl %esi,16(%ebp)\n\ movl %edi,20(%ebp)\n\ popl %ebp\n\ xorl %eax,%eax\n\ ret"); #endif #endif int rb_setjmp (rb_jmp_buf); #define jmp_buf rb_jmp_buf #define setjmp rb_setjmp #endif /* __human68k__ or DJGPP */ #endif /* __GNUC__ */ static void garbage_collect() { struct gc_list *list; struct FRAME * volatile frame; /* gcc 2.7.2.3 -O2 bug?? */ jmp_buf save_regs_gc_mark; SET_STACK_END; #ifdef HAVE_NATIVETHREAD if (!is_ruby_native_thread()) { rb_bug("cross-thread violation on rb_gc()"); } #endif if (dont_gc || during_gc) { if (!freelist) { add_heap(); } return; } if (during_gc) return; during_gc++; init_mark_stack(); gc_mark((VALUE)ruby_current_node, 0); /* mark frame stack */ for (frame = ruby_frame; frame; frame = frame->prev) { rb_gc_mark_frame(frame); if (frame->tmp) { struct FRAME *tmp = frame->tmp; while (tmp) { rb_gc_mark_frame(tmp); tmp = tmp->prev; } } } gc_mark((VALUE)ruby_scope, 0); gc_mark((VALUE)ruby_dyna_vars, 0); if (finalizer_table) { mark_tbl(finalizer_table, 0); } FLUSH_REGISTER_WINDOWS; /* This assumes that all registers are saved into the jmp_buf (and stack) */ setjmp(save_regs_gc_mark); mark_locations_array((VALUE*)save_regs_gc_mark, sizeof(save_regs_gc_mark) / sizeof(VALUE *)); #if STACK_GROW_DIRECTION < 0 rb_gc_mark_locations((VALUE*)STACK_END, rb_gc_stack_start); #elif STACK_GROW_DIRECTION > 0 rb_gc_mark_locations(rb_gc_stack_start, (VALUE*)STACK_END + 1); #else if ((VALUE*)STACK_END < rb_gc_stack_start) rb_gc_mark_locations((VALUE*)STACK_END, rb_gc_stack_start); else rb_gc_mark_locations(rb_gc_stack_start, (VALUE*)STACK_END + 1); #endif #ifdef __ia64 /* mark backing store (flushed register window on the stack) */ /* the basic idea from guile GC code */ rb_gc_mark_locations(rb_gc_register_stack_start, (VALUE*)rb_ia64_bsp()); #endif #if defined(__human68k__) || defined(__mc68000__) rb_gc_mark_locations((VALUE*)((char*)STACK_END + 2), (VALUE*)((char*)rb_gc_stack_start + 2)); #endif rb_gc_mark_threads(); /* mark protected global variables */ for (list = global_List; list; list = list->next) { rb_gc_mark_maybe(*list->varptr); } rb_mark_end_proc(); rb_gc_mark_global_tbl(); rb_mark_tbl(rb_class_tbl); rb_gc_mark_trap_list(); /* mark generic instance variables for special constants */ rb_mark_generic_ivar_tbl(); rb_gc_mark_parser(); /* gc_mark objects whose marking are not completed*/ do { while (!MARK_STACK_EMPTY) { if (mark_stack_overflow){ gc_mark_all(); } else { gc_mark_rest(); } } rb_gc_abort_threads(); } while (!MARK_STACK_EMPTY); gc_sweep(); } void rb_gc() { garbage_collect(); rb_gc_finalize_deferred(); } /* * call-seq: * GC.start => nil * gc.garbage_collect => nil * ObjectSpace.garbage_collect => nil * * Initiates garbage collection, unless manually disabled. * */ VALUE rb_gc_start() { rb_gc(); return Qnil; } void ruby_set_stack_size(size) size_t size; { #ifndef STACK_LEVEL_MAX STACK_LEVEL_MAX = size / sizeof(VALUE); #endif } void Init_stack(addr) VALUE *addr; { #ifdef __ia64 if (rb_gc_register_stack_start == 0) { # if defined(__FreeBSD__) /* * FreeBSD/ia64 currently does not have a way for a process to get the * base address for the RSE backing store, so hardcode it. */ rb_gc_register_stack_start = (4ULL<<61); # elif defined(HAVE___LIBC_IA64_REGISTER_BACKING_STORE_BASE) # pragma weak __libc_ia64_register_backing_store_base extern unsigned long __libc_ia64_register_backing_store_base; rb_gc_register_stack_start = (VALUE*)__libc_ia64_register_backing_store_base; # endif } { VALUE *bsp = (VALUE*)rb_ia64_bsp(); if (rb_gc_register_stack_start == 0 || bsp < rb_gc_register_stack_start) { rb_gc_register_stack_start = bsp; } } #endif #if defined(_WIN32) || defined(__CYGWIN__) MEMORY_BASIC_INFORMATION m; memset(&m, 0, sizeof(m)); VirtualQuery(&m, &m, sizeof(m)); rb_gc_stack_start = STACK_UPPER((VALUE *)&m, (VALUE *)m.BaseAddress, (VALUE *)((char *)m.BaseAddress + m.RegionSize) - 1); #elif defined(STACK_END_ADDRESS) { extern void *STACK_END_ADDRESS; rb_gc_stack_start = STACK_END_ADDRESS; } #else if (!addr) addr = (void *)&addr; STACK_UPPER(&addr, addr, ++addr); if (rb_gc_stack_start) { if (STACK_UPPER(&addr, rb_gc_stack_start > addr, rb_gc_stack_start < addr)) rb_gc_stack_start = addr; return; } rb_gc_stack_start = addr; #endif #ifdef HAVE_GETRLIMIT { struct rlimit rlim; if (getrlimit(RLIMIT_STACK, &rlim) == 0) { unsigned int space = rlim.rlim_cur/5; if (space > 1024*1024) space = 1024*1024; STACK_LEVEL_MAX = (rlim.rlim_cur - space) / sizeof(VALUE); } } #endif } void ruby_init_stack(VALUE *addr #ifdef __ia64 , void *bsp #endif ) { if (!rb_gc_stack_start || STACK_UPPER(&addr, rb_gc_stack_start > addr, rb_gc_stack_start < addr)) { rb_gc_stack_start = addr; } #ifdef __ia64 if (!rb_gc_register_stack_start || (VALUE*)bsp < rb_gc_register_stack_start) { rb_gc_register_stack_start = (VALUE*)bsp; } #endif #ifdef HAVE_GETRLIMIT { struct rlimit rlim; if (getrlimit(RLIMIT_STACK, &rlim) == 0) { unsigned int space = rlim.rlim_cur/5; if (space > 1024*1024) space = 1024*1024; STACK_LEVEL_MAX = (rlim.rlim_cur - space) / sizeof(VALUE); } } #elif defined _WIN32 { MEMORY_BASIC_INFORMATION mi; DWORD size; DWORD space; if (VirtualQuery(&mi, &mi, sizeof(mi))) { size = (char *)mi.BaseAddress - (char *)mi.AllocationBase; space = size / 5; if (space > 1024*1024) space = 1024*1024; STACK_LEVEL_MAX = (size - space) / sizeof(VALUE); } } #endif } /* * Document-class: ObjectSpace * * The ObjectSpace module contains a number of routines * that interact with the garbage collection facility and allow you to * traverse all living objects with an iterator. * * ObjectSpace also provides support for object * finalizers, procs that will be called when a specific object is * about to be destroyed by garbage collection. * * include ObjectSpace * * * a = "A" * b = "B" * c = "C" * * * define_finalizer(a, proc {|id| puts "Finalizer one on #{id}" }) * define_finalizer(a, proc {|id| puts "Finalizer two on #{id}" }) * define_finalizer(b, proc {|id| puts "Finalizer three on #{id}" }) * * produces: * * Finalizer three on 537763470 * Finalizer one on 537763480 * Finalizer two on 537763480 * */ void Init_heap() { if (!rb_gc_stack_start) { Init_stack(0); } add_heap(); } static VALUE os_obj_of(of) VALUE of; { int i; int n = 0; for (i = 0; i < heaps_used; i++) { RVALUE *p, *pend; p = heaps[i].slot; pend = p + heaps[i].limit; for (;p < pend; p++) { if (p->as.basic.flags) { switch (BUILTIN_TYPE(p)) { case T_NONE: case T_ICLASS: case T_VARMAP: case T_SCOPE: case T_NODE: case T_DEFERRED: continue; case T_CLASS: if (FL_TEST(p, FL_SINGLETON)) continue; default: if (!p->as.basic.klass) continue; if (!of || rb_obj_is_kind_of((VALUE)p, of)) { rb_yield((VALUE)p); n++; } } } } } return INT2FIX(n); } /* * call-seq: * ObjectSpace.each_object([module]) {|obj| ... } => fixnum * * Calls the block once for each living, nonimmediate object in this * Ruby process. If module is specified, calls the block * for only those classes or modules that match (or are a subclass of) * module. Returns the number of objects found. Immediate * objects (Fixnums, Symbols * true, false, and nil) are * never returned. In the example below, each_object * returns both the numbers we defined and several constants defined in * the Math module. * * a = 102.7 * b = 95 # Won't be returned * c = 12345678987654321 * count = ObjectSpace.each_object(Numeric) {|x| p x } * puts "Total count: #{count}" * * produces: * * 12345678987654321 * 102.7 * 2.71828182845905 * 3.14159265358979 * 2.22044604925031e-16 * 1.7976931348623157e+308 * 2.2250738585072e-308 * Total count: 7 * */ static VALUE os_each_obj(argc, argv) int argc; VALUE *argv; { VALUE of; rb_secure(4); if (rb_scan_args(argc, argv, "01", &of) == 0) { of = 0; } return os_obj_of(of); } static VALUE finalizers; /* deprecated */ static VALUE add_final(os, block) VALUE os, block; { rb_warn("ObjectSpace::add_finalizer is deprecated; use define_finalizer"); if (!rb_respond_to(block, rb_intern("call"))) { rb_raise(rb_eArgError, "wrong type argument %s (should be callable)", rb_obj_classname(block)); } rb_ary_push(finalizers, block); return block; } /* * deprecated */ static VALUE rm_final(os, block) VALUE os, block; { rb_warn("ObjectSpace::remove_finalizer is deprecated; use undefine_finalizer"); rb_ary_delete(finalizers, block); return block; } /* * deprecated */ static VALUE finals() { rb_warn("ObjectSpace::finalizers is deprecated"); return finalizers; } /* * deprecated */ static VALUE call_final(os, obj) VALUE os, obj; { rb_warn("ObjectSpace::call_finalizer is deprecated; use define_finalizer"); need_call_final = 1; FL_SET(obj, FL_FINALIZE); return obj; } /* * call-seq: * ObjectSpace.undefine_finalizer(obj) * * Removes all finalizers for obj. * */ static VALUE undefine_final(os, obj) VALUE os, obj; { if (finalizer_table) { st_delete(finalizer_table, (st_data_t*)&obj, 0); } return obj; } /* * call-seq: * ObjectSpace.define_finalizer(obj, aProc=proc()) * * Adds aProc as a finalizer, to be called after obj * was destroyed. * */ static VALUE define_final(argc, argv, os) int argc; VALUE *argv; VALUE os; { VALUE obj, block, table; rb_scan_args(argc, argv, "11", &obj, &block); if (argc == 1) { block = rb_block_proc(); } else if (!rb_respond_to(block, rb_intern("call"))) { rb_raise(rb_eArgError, "wrong type argument %s (should be callable)", rb_obj_classname(block)); } need_call_final = 1; if (!FL_ABLE(obj)) { rb_raise(rb_eArgError, "cannot define finalizer for %s", rb_obj_classname(obj)); } RBASIC(obj)->flags |= FL_FINALIZE; block = rb_ary_new3(2, INT2FIX(ruby_safe_level), block); OBJ_FREEZE(block); if (!finalizer_table) { finalizer_table = st_init_numtable(); } if (st_lookup(finalizer_table, obj, &table)) { rb_ary_push(table, block); } else { table = rb_ary_new3(1, block); RBASIC(table)->klass = 0; st_add_direct(finalizer_table, obj, table); } return block; } void rb_gc_copy_finalizer(dest, obj) VALUE dest, obj; { VALUE table; if (!finalizer_table) return; if (!FL_TEST(obj, FL_FINALIZE)) return; if (st_lookup(finalizer_table, obj, &table)) { st_insert(finalizer_table, dest, table); } RBASIC(dest)->flags |= FL_FINALIZE; } static VALUE run_single_final(args) VALUE *args; { rb_eval_cmd(args[0], args[1], (int)args[2]); return Qnil; } static void run_final(obj) VALUE obj; { long i; int status, critical_save = rb_thread_critical; VALUE args[3], table, objid; objid = rb_obj_id(obj); /* make obj into id */ rb_thread_critical = Qtrue; /* NOTE: This change below, adding DATA_PTR(obj) to the if line, is a stopgap fix for segfaults; the reason for DATA_PTR(obj) == 0 needs to be found and fixed. */ if (BUILTIN_TYPE(obj) == T_DEFERRED && RDATA(obj)->dfree && DATA_PTR(obj)) { (*RDATA(obj)->dfree)(DATA_PTR(obj)); } args[1] = 0; args[2] = (VALUE)ruby_safe_level; for (i=0; ilen; i++) { args[0] = RARRAY(finalizers)->ptr[i]; if (!args[1]) args[1] = rb_ary_new3(1, objid); rb_protect((VALUE(*)_((VALUE)))run_single_final, (VALUE)args, &status); } if (finalizer_table && st_delete(finalizer_table, (st_data_t*)&obj, &table)) { for (i=0; ilen; i++) { VALUE final = RARRAY(table)->ptr[i]; args[0] = RARRAY(final)->ptr[1]; if (!args[1]) args[1] = rb_ary_new3(1, objid); args[2] = FIX2INT(RARRAY(final)->ptr[0]); rb_protect((VALUE(*)_((VALUE)))run_single_final, (VALUE)args, &status); } } rb_thread_critical = critical_save; } void rb_gc_finalize_deferred() { RVALUE *p = deferred_final_list; deferred_final_list = 0; if (p) { finalize_list(p); free_unused_heaps(); } } static int chain_finalized_object(st_data_t key, st_data_t val, st_data_t arg) { RVALUE *p = (RVALUE *)key, **final_list = (RVALUE **)arg; if ((p->as.basic.flags & (FL_FINALIZE|FL_MARK)) == FL_FINALIZE) { if (BUILTIN_TYPE(p) != T_DEFERRED) { p->as.free.flags = FL_MARK | T_DEFERRED; /* remain marked */ RDATA(p)->dfree = 0; } p->as.free.next = *final_list; *final_list = p; } return ST_CONTINUE; } void rb_gc_call_finalizer_at_exit() { RVALUE *p, *pend; int i; /* run finalizers */ if (need_call_final) { do { p = deferred_final_list; deferred_final_list = 0; finalize_list(p); mark_tbl(finalizer_table, 0); st_foreach(finalizer_table, chain_finalized_object, (st_data_t)&deferred_final_list); } while (deferred_final_list); } /* run data object's finalizers */ for (i = 0; i < heaps_used; i++) { p = heaps[i].slot; pend = p + heaps[i].limit; while (p < pend) { if (BUILTIN_TYPE(p) == T_DATA && DATA_PTR(p) && RANY(p)->as.data.dfree && RANY(p)->as.basic.klass != rb_cThread) { p->as.free.flags = 0; if ((long)RANY(p)->as.data.dfree == -1) { RUBY_CRITICAL(free(DATA_PTR(p))); } else if (RANY(p)->as.data.dfree) { (*RANY(p)->as.data.dfree)(DATA_PTR(p)); } } else if (BUILTIN_TYPE(p) == T_FILE) { p->as.free.flags = 0; rb_io_fptr_finalize(RANY(p)->as.file.fptr); } p++; } } } /* * call-seq: * ObjectSpace._id2ref(object_id) -> an_object * * Converts an object id to a reference to the object. May not be * called on an object id passed as a parameter to a finalizer. * * s = "I am a string" #=> "I am a string" * r = ObjectSpace._id2ref(s.object_id) #=> "I am a string" * r == s #=> true * */ static VALUE id2ref(obj, objid) VALUE obj, objid; { unsigned long ptr, p0; int type; rb_secure(4); p0 = ptr = NUM2ULONG(objid); if (ptr == Qtrue) return Qtrue; if (ptr == Qfalse) return Qfalse; if (ptr == Qnil) return Qnil; if (FIXNUM_P(ptr)) return (VALUE)ptr; ptr = objid ^ FIXNUM_FLAG; /* unset FIXNUM_FLAG */ if ((ptr % sizeof(RVALUE)) == (4 << 2)) { ID symid = ptr / sizeof(RVALUE); if (rb_id2name(symid) == 0) rb_raise(rb_eRangeError, "%p is not symbol id value", p0); return ID2SYM(symid); } if (!is_pointer_to_heap((void *)ptr)|| (type = BUILTIN_TYPE(ptr)) > T_SYMBOL || type == T_ICLASS) { rb_raise(rb_eRangeError, "0x%lx is not id value", p0); } if (BUILTIN_TYPE(ptr) == 0 || RBASIC(ptr)->klass == 0) { rb_raise(rb_eRangeError, "0x%lx is recycled object", p0); } return (VALUE)ptr; } /* * Document-method: __id__ * Document-method: object_id * * call-seq: * obj.__id__ => fixnum * obj.object_id => fixnum * * Returns an integer identifier for obj. The same number will * be returned on all calls to id for a given object, and * no two active objects will share an id. * Object#object_id is a different concept from the * :name notation, which returns the symbol id of * name. Replaces the deprecated Object#id. */ /* * call-seq: * obj.hash => fixnum * * Generates a Fixnum hash value for this object. This * function must have the property that a.eql?(b) implies * a.hash == b.hash. The hash value is used by class * Hash. Any hash value that exceeds the capacity of a * Fixnum will be truncated before being used. */ VALUE rb_obj_id(VALUE obj) { /* * 32-bit VALUE space * MSB ------------------------ LSB * false 00000000000000000000000000000000 * true 00000000000000000000000000000010 * nil 00000000000000000000000000000100 * undef 00000000000000000000000000000110 * symbol ssssssssssssssssssssssss00001110 * object oooooooooooooooooooooooooooooo00 = 0 (mod sizeof(RVALUE)) * fixnum fffffffffffffffffffffffffffffff1 * * object_id space * LSB * false 00000000000000000000000000000000 * true 00000000000000000000000000000010 * nil 00000000000000000000000000000100 * undef 00000000000000000000000000000110 * symbol 000SSSSSSSSSSSSSSSSSSSSSSSSSSS0 S...S % A = 4 (S...S = s...s * A + 4) * object oooooooooooooooooooooooooooooo0 o...o % A = 0 * fixnum fffffffffffffffffffffffffffffff1 bignum if required * * where A = sizeof(RVALUE)/4 * * sizeof(RVALUE) is * 20 if 32-bit, double is 4-byte aligned * 24 if 32-bit, double is 8-byte aligned * 40 if 64-bit */ if (TYPE(obj) == T_SYMBOL) { return (SYM2ID(obj) * sizeof(RVALUE) + (4 << 2)) | FIXNUM_FLAG; } if (SPECIAL_CONST_P(obj)) { return LONG2NUM((long)obj); } return (VALUE)((long)obj|FIXNUM_FLAG); } /* * The GC module provides an interface to Ruby's mark and * sweep garbage collection mechanism. Some of the underlying methods * are also available via the ObjectSpace module. */ void Init_GC() { VALUE rb_mObSpace; rb_mGC = rb_define_module("GC"); rb_define_singleton_method(rb_mGC, "start", rb_gc_start, 0); rb_define_singleton_method(rb_mGC, "enable", rb_gc_enable, 0); rb_define_singleton_method(rb_mGC, "disable", rb_gc_disable, 0); rb_define_method(rb_mGC, "garbage_collect", rb_gc_start, 0); rb_mObSpace = rb_define_module("ObjectSpace"); rb_define_module_function(rb_mObSpace, "each_object", os_each_obj, -1); rb_define_module_function(rb_mObSpace, "garbage_collect", rb_gc_start, 0); rb_define_module_function(rb_mObSpace, "add_finalizer", add_final, 1); rb_define_module_function(rb_mObSpace, "remove_finalizer", rm_final, 1); rb_define_module_function(rb_mObSpace, "finalizers", finals, 0); rb_define_module_function(rb_mObSpace, "call_finalizer", call_final, 1); rb_define_module_function(rb_mObSpace, "define_finalizer", define_final, -1); rb_define_module_function(rb_mObSpace, "undefine_finalizer", undefine_final, 1); rb_define_module_function(rb_mObSpace, "_id2ref", id2ref, 1); rb_gc_register_address(&rb_mObSpace); rb_global_variable(&finalizers); rb_gc_unregister_address(&rb_mObSpace); finalizers = rb_ary_new(); source_filenames = st_init_strtable(); rb_global_variable(&nomem_error); nomem_error = rb_exc_new3(rb_eNoMemError, rb_obj_freeze(rb_str_new2("failed to allocate memory"))); OBJ_TAINT(nomem_error); OBJ_FREEZE(nomem_error); rb_define_method(rb_mKernel, "hash", rb_obj_id, 0); rb_define_method(rb_mKernel, "__id__", rb_obj_id, 0); rb_define_method(rb_mKernel, "object_id", rb_obj_id, 0); } /********************************************************************** hash.c - $Author: shyouhei $ $Date: 2007-08-22 05:42:36 +0200 (Wed, 22 Aug 2007) $ created at: Mon Nov 22 18:51:18 JST 1993 Copyright (C) 1993-2003 Yukihiro Matsumoto Copyright (C) 2000 Network Applied Communication Laboratory, Inc. Copyright (C) 2000 Information-technology Promotion Agency, Japan **********************************************************************/ #include "ruby.h" #include "st.h" #include "util.h" #include "rubysig.h" #ifdef __APPLE__ #include #endif #define HASH_DELETED FL_USER1 #define HASH_PROC_DEFAULT FL_USER2 static void rb_hash_modify(hash) VALUE hash; { if (!RHASH(hash)->tbl) rb_raise(rb_eTypeError, "uninitialized Hash"); if (OBJ_FROZEN(hash)) rb_error_frozen("hash"); if (!OBJ_TAINTED(hash) && rb_safe_level() >= 4) rb_raise(rb_eSecurityError, "Insecure: can't modify hash"); } VALUE rb_hash_freeze(hash) VALUE hash; { return rb_obj_freeze(hash); } VALUE rb_cHash; static VALUE envtbl; static ID id_hash, id_call, id_default; static VALUE eql(args) VALUE *args; { return (VALUE)rb_eql(args[0], args[1]); } static int rb_any_cmp(a, b) VALUE a, b; { VALUE args[2]; if (a == b) return 0; if (FIXNUM_P(a) && FIXNUM_P(b)) { return a != b; } if (TYPE(a) == T_STRING && RBASIC(a)->klass == rb_cString && TYPE(b) == T_STRING && RBASIC(b)->klass == rb_cString) { return rb_str_cmp(a, b); } if (a == Qundef || b == Qundef) return -1; if (SYMBOL_P(a) && SYMBOL_P(b)) { return a != b; } args[0] = a; args[1] = b; return !rb_with_disable_interrupt(eql, (VALUE)args); } VALUE rb_hash(obj) VALUE obj; { return rb_funcall(obj, id_hash, 0); } static int rb_any_hash(a) VALUE a; { VALUE hval; switch (TYPE(a)) { case T_FIXNUM: case T_SYMBOL: return (int)a; break; case T_STRING: return rb_str_hash(a); break; default: hval = rb_funcall(a, id_hash, 0); if (!FIXNUM_P(hval)) { hval = rb_funcall(hval, '%', 1, INT2FIX(536870923)); } return (int)FIX2LONG(hval); } } static struct st_hash_type objhash = { rb_any_cmp, rb_any_hash, }; struct foreach_safe_arg { st_table *tbl; int (*func)(); st_data_t arg; }; static int foreach_safe_i(key, value, arg) st_data_t key, value; struct foreach_safe_arg *arg; { int status; if (key == Qundef) return ST_CONTINUE; status = (*arg->func)(key, value, arg->arg); if (status == ST_CONTINUE) { return ST_CHECK; } return status; } void st_foreach_safe(table, func, a) st_table *table; int (*func)(); st_data_t a; { struct foreach_safe_arg arg; arg.tbl = table; arg.func = func; arg.arg = a; if (st_foreach(table, foreach_safe_i, (st_data_t)&arg)) { rb_raise(rb_eRuntimeError, "hash modified during iteration"); } } struct hash_foreach_arg { VALUE hash; int (*func)(); VALUE arg; }; static int hash_foreach_iter(key, value, arg) VALUE key, value; struct hash_foreach_arg *arg; { int status; st_table *tbl; tbl = RHASH(arg->hash)->tbl; if (key == Qundef) return ST_CONTINUE; status = (*arg->func)(key, value, arg->arg); if (RHASH(arg->hash)->tbl != tbl) { rb_raise(rb_eRuntimeError, "rehash occurred during iteration"); } switch (status) { case ST_DELETE: st_delete_safe(tbl, (st_data_t*)&key, 0, Qundef); FL_SET(arg->hash, HASH_DELETED); case ST_CONTINUE: break; case ST_STOP: return ST_STOP; } return ST_CHECK; } static VALUE hash_foreach_ensure(hash) VALUE hash; { RHASH(hash)->iter_lev--; if (RHASH(hash)->iter_lev == 0) { if (FL_TEST(hash, HASH_DELETED)) { st_cleanup_safe(RHASH(hash)->tbl, Qundef); FL_UNSET(hash, HASH_DELETED); } } return 0; } static VALUE hash_foreach_call(arg) struct hash_foreach_arg *arg; { if (st_foreach(RHASH(arg->hash)->tbl, hash_foreach_iter, (st_data_t)arg)) { rb_raise(rb_eRuntimeError, "hash modified during iteration"); } return Qnil; } void rb_hash_foreach(hash, func, farg) VALUE hash; int (*func)(); VALUE farg; { struct hash_foreach_arg arg; RHASH(hash)->iter_lev++; arg.hash = hash; arg.func = func; arg.arg = farg; rb_ensure(hash_foreach_call, (VALUE)&arg, hash_foreach_ensure, hash); } static VALUE hash_alloc0 _((VALUE)); static VALUE hash_alloc _((VALUE)); static VALUE hash_alloc0(klass) VALUE klass; { NEWOBJ(hash, struct RHash); OBJSETUP(hash, klass, T_HASH); hash->ifnone = Qnil; return (VALUE)hash; } static VALUE hash_alloc(klass) VALUE klass; { VALUE hash = hash_alloc0(klass); RHASH(hash)->tbl = st_init_table(&objhash); return hash; } VALUE rb_hash_new() { return hash_alloc(rb_cHash); } /* * call-seq: * Hash.new => hash * Hash.new(obj) => aHash * Hash.new {|hash, key| block } => aHash * * Returns a new, empty hash. If this hash is subsequently accessed by * a key that doesn't correspond to a hash entry, the value returned * depends on the style of new used to create the hash. In * the first form, the access returns nil. If * obj is specified, this single object will be used for * all default values. If a block is specified, it will be * called with the hash object and the key, and should return the * default value. It is the block's responsibility to store the value * in the hash if required. * * h = Hash.new("Go Fish") * h["a"] = 100 * h["b"] = 200 * h["a"] #=> 100 * h["c"] #=> "Go Fish" * # The following alters the single default object * h["c"].upcase! #=> "GO FISH" * h["d"] #=> "GO FISH" * h.keys #=> ["a", "b"] * * # While this creates a new default object each time * h = Hash.new { |hash, key| hash[key] = "Go Fish: #{key}" } * h["c"] #=> "Go Fish: c" * h["c"].upcase! #=> "GO FISH: C" * h["d"] #=> "Go Fish: d" * h.keys #=> ["c", "d"] * */ static VALUE rb_hash_initialize(argc, argv, hash) int argc; VALUE *argv; VALUE hash; { VALUE ifnone; rb_hash_modify(hash); if (rb_block_given_p()) { if (argc > 0) { rb_raise(rb_eArgError, "wrong number of arguments"); } RHASH(hash)->ifnone = rb_block_proc(); FL_SET(hash, HASH_PROC_DEFAULT); } else { rb_scan_args(argc, argv, "01", &ifnone); RHASH(hash)->ifnone = ifnone; } return hash; } /* * call-seq: * Hash[ [key =>|, value]* ] => hash * * Creates a new hash populated with the given objects. Equivalent to * the literal { key, value, ... }. Keys and * values occur in pairs, so there must be an even number of arguments. * * Hash["a", 100, "b", 200] #=> {"a"=>100, "b"=>200} * Hash["a" => 100, "b" => 200] #=> {"a"=>100, "b"=>200} * { "a" => 100, "b" => 200 } #=> {"a"=>100, "b"=>200} */ static VALUE rb_hash_s_create(argc, argv, klass) int argc; VALUE *argv; VALUE klass; { VALUE hash; int i; if (argc == 1 && TYPE(argv[0]) == T_HASH) { hash = hash_alloc0(klass); RHASH(hash)->tbl = st_copy(RHASH(argv[0])->tbl); return hash; } if (argc % 2 != 0) { rb_raise(rb_eArgError, "odd number of arguments for Hash"); } hash = hash_alloc(klass); for (i=0; i hsh * * Rebuilds the hash based on the current hash values for each key. If * values of key objects have changed since they were inserted, this * method will reindex hsh. If Hash#rehash is * called while an iterator is traversing the hash, an * IndexError will be raised in the iterator. * * a = [ "a", "b" ] * c = [ "c", "d" ] * h = { a => 100, c => 300 } * h[a] #=> 100 * a[0] = "z" * h[a] #=> nil * h.rehash #=> {["z", "b"]=>100, ["c", "d"]=>300} * h[a] #=> 100 */ static VALUE rb_hash_rehash(hash) VALUE hash; { st_table *tbl; rb_hash_modify(hash); tbl = st_init_table_with_size(&objhash, RHASH(hash)->tbl->num_entries); rb_hash_foreach(hash, rb_hash_rehash_i, (st_data_t)tbl); st_free_table(RHASH(hash)->tbl); RHASH(hash)->tbl = tbl; return hash; } /* * call-seq: * hsh[key] => value * * Element Reference---Retrieves the value object corresponding * to the key object. If not found, returns the a default value (see * Hash::new for details). * * h = { "a" => 100, "b" => 200 } * h["a"] #=> 100 * h["c"] #=> nil * */ VALUE rb_hash_aref(hash, key) VALUE hash, key; { VALUE val; if (!st_lookup(RHASH(hash)->tbl, key, &val)) { return rb_funcall(hash, id_default, 1, key); } return val; } /* * call-seq: * hsh.fetch(key [, default] ) => obj * hsh.fetch(key) {| key | block } => obj * * Returns a value from the hash for the given key. If the key can't be * found, there are several options: With no other arguments, it will * raise an IndexError exception; if default is * given, then that will be returned; if the optional code block is * specified, then that will be run and its result returned. * * h = { "a" => 100, "b" => 200 } * h.fetch("a") #=> 100 * h.fetch("z", "go fish") #=> "go fish" * h.fetch("z") { |el| "go fish, #{el}"} #=> "go fish, z" * * The following example shows that an exception is raised if the key * is not found and a default value is not supplied. * * h = { "a" => 100, "b" => 200 } * h.fetch("z") * * produces: * * prog.rb:2:in `fetch': key not found (IndexError) * from prog.rb:2 * */ static VALUE rb_hash_fetch(argc, argv, hash) int argc; VALUE *argv; VALUE hash; { VALUE key, if_none; VALUE val; long block_given; rb_scan_args(argc, argv, "11", &key, &if_none); block_given = rb_block_given_p(); if (block_given && argc == 2) { rb_warn("block supersedes default value argument"); } if (!st_lookup(RHASH(hash)->tbl, key, &val)) { if (block_given) return rb_yield(key); if (argc == 1) { rb_raise(rb_eIndexError, "key not found"); } return if_none; } return val; } /* * call-seq: * hsh.default(key=nil) => obj * * Returns the default value, the value that would be returned by * hsh[key] if key did not exist in hsh. * See also Hash::new and Hash#default=. * * h = Hash.new #=> {} * h.default #=> nil * h.default(2) #=> nil * * h = Hash.new("cat") #=> {} * h.default #=> "cat" * h.default(2) #=> "cat" * * h = Hash.new {|h,k| h[k] = k.to_i*10} #=> {} * h.default #=> 0 * h.default(2) #=> 20 */ static VALUE rb_hash_default(argc, argv, hash) int argc; VALUE *argv; VALUE hash; { VALUE key; rb_scan_args(argc, argv, "01", &key); if (FL_TEST(hash, HASH_PROC_DEFAULT)) { if (argc == 0) return Qnil; return rb_funcall(RHASH(hash)->ifnone, id_call, 2, hash, key); } return RHASH(hash)->ifnone; } /* * call-seq: * hsh.default = obj => hsh * * Sets the default value, the value returned for a key that does not * exist in the hash. It is not possible to set the a default to a * Proc that will be executed on each key lookup. * * h = { "a" => 100, "b" => 200 } * h.default = "Go fish" * h["a"] #=> 100 * h["z"] #=> "Go fish" * # This doesn't do what you might hope... * h.default = proc do |hash, key| * hash[key] = key + key * end * h[2] #=> # * h["cat"] #=> # */ static VALUE rb_hash_set_default(hash, ifnone) VALUE hash, ifnone; { rb_hash_modify(hash); RHASH(hash)->ifnone = ifnone; FL_UNSET(hash, HASH_PROC_DEFAULT); return ifnone; } /* * call-seq: * hsh.default_proc -> anObject * * If Hash::new was invoked with a block, return that * block, otherwise return nil. * * h = Hash.new {|h,k| h[k] = k*k } #=> {} * p = h.default_proc #=> # * a = [] #=> [] * p.call(a, 2) * a #=> [nil, nil, 4] */ static VALUE rb_hash_default_proc(hash) VALUE hash; { if (FL_TEST(hash, HASH_PROC_DEFAULT)) { return RHASH(hash)->ifnone; } return Qnil; } static int index_i(key, value, args) VALUE key, value; VALUE *args; { if (rb_equal(value, args[0])) { args[1] = key; return ST_STOP; } return ST_CONTINUE; } static VALUE rb_hash_delete_key(hash, key) VALUE hash, key; { st_data_t ktmp = (st_data_t)key, val; if (RHASH(hash)->iter_lev > 0) { if (st_delete_safe(RHASH(hash)->tbl, &ktmp, &val, Qundef)) { FL_SET(hash, HASH_DELETED); return (VALUE)val; } } else if (st_delete(RHASH(hash)->tbl, &ktmp, &val)) return (VALUE)val; return Qundef; } /* * call-seq: * hsh.index(value) => key * * Returns the key for a given value. If not found, returns nil. * * h = { "a" => 100, "b" => 200 } * h.index(200) #=> "b" * h.index(999) #=> nil * */ static VALUE rb_hash_index(hash, value) VALUE hash, value; { VALUE args[2]; args[0] = value; args[1] = Qnil; rb_hash_foreach(hash, index_i, (st_data_t)args); return args[1]; } /* * call-seq: * hsh.indexes(key, ...) => array * hsh.indices(key, ...) => array * * Deprecated in favor of Hash#select. * */ static VALUE rb_hash_indexes(argc, argv, hash) int argc; VALUE *argv; VALUE hash; { VALUE indexes; int i; rb_warn("Hash#%s is deprecated; use Hash#values_at", rb_id2name(rb_frame_last_func())); indexes = rb_ary_new2(argc); for (i=0; iptr[i] = rb_hash_aref(hash, argv[i]); RARRAY(indexes)->len++; } return indexes; } /* * call-seq: * hsh.delete(key) => value * hsh.delete(key) {| key | block } => value * * Deletes and returns a key-value pair from hsh whose key is * equal to key. If the key is not found, returns the * default value. If the optional code block is given and the * key is not found, pass in the key and return the result of * block. * * h = { "a" => 100, "b" => 200 } * h.delete("a") #=> 100 * h.delete("z") #=> nil * h.delete("z") { |el| "#{el} not found" } #=> "z not found" * */ VALUE rb_hash_delete(hash, key) VALUE hash, key; { VALUE val; rb_hash_modify(hash); val = rb_hash_delete_key(hash, key); if (val != Qundef) return val; if (rb_block_given_p()) { return rb_yield(key); } return Qnil; } struct shift_var { VALUE key; VALUE val; }; static int shift_i(key, value, var) VALUE key, value; struct shift_var *var; { if (key == Qundef) return ST_CONTINUE; if (var->key != Qundef) return ST_STOP; var->key = key; var->val = value; return ST_DELETE; } static int shift_i_safe(key, value, var) VALUE key, value; struct shift_var *var; { if (key == Qundef) return ST_CONTINUE; var->key = key; var->val = value; return ST_STOP; } /* * call-seq: * hsh.shift -> anArray or obj * * Removes a key-value pair from hsh and returns it as the * two-item array [ key, value ], or * the hash's default value if the hash is empty. * * h = { 1 => "a", 2 => "b", 3 => "c" } * h.shift #=> [1, "a"] * h #=> {2=>"b", 3=>"c"} */ static VALUE rb_hash_shift(hash) VALUE hash; { struct shift_var var; rb_hash_modify(hash); var.key = Qundef; if (RHASH(hash)->iter_lev > 0) { rb_hash_foreach(hash, shift_i_safe, (st_data_t)&var); if (var.key != Qundef) { st_data_t key = var.key; if (st_delete_safe(RHASH(hash)->tbl, &key, 0, Qundef)) { FL_SET(hash, HASH_DELETED); } } } else { rb_hash_foreach(hash, shift_i, (st_data_t)&var); } if (var.key != Qundef) { return rb_assoc_new(var.key, var.val); } else if (FL_TEST(hash, HASH_PROC_DEFAULT)) { return rb_funcall(RHASH(hash)->ifnone, id_call, 2, hash, Qnil); } else { return RHASH(hash)->ifnone; } } static int delete_if_i(key, value, hash) VALUE key, value, hash; { if (key == Qundef) return ST_CONTINUE; if (RTEST(rb_yield_values(2, key, value))) { rb_hash_delete_key(hash, key); } return ST_CONTINUE; } /* * call-seq: * hsh.delete_if {| key, value | block } -> hsh * * Deletes every key-value pair from hsh for which block * evaluates to true. * * h = { "a" => 100, "b" => 200, "c" => 300 } * h.delete_if {|key, value| key >= "b" } #=> {"a"=>100} * */ VALUE rb_hash_delete_if(hash) VALUE hash; { rb_hash_modify(hash); rb_hash_foreach(hash, delete_if_i, hash); return hash; } /* * call-seq: * hsh.reject! {| key, value | block } -> hsh or nil * * Equivalent to Hash#delete_if, but returns * nil if no changes were made. */ VALUE rb_hash_reject_bang(hash) VALUE hash; { int n = RHASH(hash)->tbl->num_entries; rb_hash_delete_if(hash); if (n == RHASH(hash)->tbl->num_entries) return Qnil; return hash; } /* * call-seq: * hsh.reject {| key, value | block } -> a_hash * * Same as Hash#delete_if, but works on (and returns) a * copy of the hsh. Equivalent to * hsh.dup.delete_if. * */ static VALUE rb_hash_reject(hash) VALUE hash; { return rb_hash_delete_if(rb_obj_dup(hash)); } static int select_i(key, value, result) VALUE key, value, result; { if (key == Qundef) return ST_CONTINUE; if (RTEST(rb_yield_values(2, key, value))) rb_ary_push(result, rb_assoc_new(key, value)); return ST_CONTINUE; } /* * call-seq: * hsh.values_at(key, ...) => array * * Return an array containing the values associated with the given keys. * Also see Hash.select. * * h = { "cat" => "feline", "dog" => "canine", "cow" => "bovine" } * h.values_at("cow", "cat") #=> ["bovine", "feline"] */ VALUE rb_hash_values_at(argc, argv, hash) int argc; VALUE *argv; VALUE hash; { VALUE result = rb_ary_new(); long i; for (i=0; i array * * Returns a new array consisting of [key,value] * pairs for which the block returns true. * Also see Hash.values_at. * * h = { "a" => 100, "b" => 200, "c" => 300 } * h.select {|k,v| k > "a"} #=> [["b", 200], ["c", 300]] * h.select {|k,v| v < 200} #=> [["a", 100]] */ VALUE rb_hash_select(argc, argv, hash) int argc; VALUE *argv; VALUE hash; { VALUE result; if (argc > 0) { rb_raise(rb_eArgError, "wrong number of arguments (%d for 0)", argc); } result = rb_ary_new(); rb_hash_foreach(hash, select_i, result); return result; } static int clear_i(key, value, dummy) VALUE key, value, dummy; { return ST_DELETE; } /* * call-seq: * hsh.clear -> hsh * * Removes all key-value pairs from hsh. * * h = { "a" => 100, "b" => 200 } #=> {"a"=>100, "b"=>200} * h.clear #=> {} * */ static VALUE rb_hash_clear(hash) VALUE hash; { rb_hash_modify(hash); if (RHASH(hash)->tbl->num_entries > 0) { rb_hash_foreach(hash, clear_i, 0); } return hash; } /* * call-seq: * hsh[key] = value => value * hsh.store(key, value) => value * * Element Assignment---Associates the value given by * value with the key given by key. * key should not have its value changed while it is in * use as a key (a String passed as a key will be * duplicated and frozen). * * h = { "a" => 100, "b" => 200 } * h["a"] = 9 * h["c"] = 4 * h #=> {"a"=>9, "b"=>200, "c"=>4} * */ VALUE rb_hash_aset(hash, key, val) VALUE hash, key, val; { rb_hash_modify(hash); if (TYPE(key) != T_STRING || st_lookup(RHASH(hash)->tbl, key, 0)) { st_insert(RHASH(hash)->tbl, key, val); } else { st_add_direct(RHASH(hash)->tbl, rb_str_new4(key), val); } return val; } static int replace_i(key, val, hash) VALUE key, val, hash; { if (key != Qundef) { rb_hash_aset(hash, key, val); } return ST_CONTINUE; } /* * call-seq: * hsh.replace(other_hash) -> hsh * * Replaces the contents of hsh with the contents of * other_hash. * * h = { "a" => 100, "b" => 200 } * h.replace({ "c" => 300, "d" => 400 }) #=> {"c"=>300, "d"=>400} * */ static VALUE rb_hash_replace(hash, hash2) VALUE hash, hash2; { hash2 = to_hash(hash2); if (hash == hash2) return hash; rb_hash_clear(hash); rb_hash_foreach(hash2, replace_i, hash); RHASH(hash)->ifnone = RHASH(hash2)->ifnone; if (FL_TEST(hash2, HASH_PROC_DEFAULT)) { FL_SET(hash, HASH_PROC_DEFAULT); } else { FL_UNSET(hash, HASH_PROC_DEFAULT); } return hash; } /* * call-seq: * hsh.length => fixnum * hsh.size => fixnum * * Returns the number of key-value pairs in the hash. * * h = { "d" => 100, "a" => 200, "v" => 300, "e" => 400 } * h.length #=> 4 * h.delete("a") #=> 200 * h.length #=> 3 */ static VALUE rb_hash_size(hash) VALUE hash; { return INT2FIX(RHASH(hash)->tbl->num_entries); } /* * call-seq: * hsh.empty? => true or false * * Returns true if hsh contains no key-value pairs. * * {}.empty? #=> true * */ static VALUE rb_hash_empty_p(hash) VALUE hash; { if (RHASH(hash)->tbl->num_entries == 0) return Qtrue; return Qfalse; } static int each_value_i(key, value) VALUE key, value; { if (key == Qundef) return ST_CONTINUE; rb_yield(value); return ST_CONTINUE; } /* * call-seq: * hsh.each_value {| value | block } -> hsh * * Calls block once for each key in hsh, passing the * value as a parameter. * * h = { "a" => 100, "b" => 200 } * h.each_value {|value| puts value } * * produces: * * 100 * 200 */ static VALUE rb_hash_each_value(hash) VALUE hash; { rb_hash_foreach(hash, each_value_i, 0); return hash; } static int each_key_i(key, value) VALUE key, value; { if (key == Qundef) return ST_CONTINUE; rb_yield(key); return ST_CONTINUE; } /* * call-seq: * hsh.each_key {| key | block } -> hsh * * Calls block once for each key in hsh, passing the key * as a parameter. * * h = { "a" => 100, "b" => 200 } * h.each_key {|key| puts key } * * produces: * * a * b */ static VALUE rb_hash_each_key(hash) VALUE hash; { rb_hash_foreach(hash, each_key_i, 0); return hash; } static int each_pair_i(key, value) VALUE key, value; { if (key == Qundef) return ST_CONTINUE; rb_yield_values(2, key, value); return ST_CONTINUE; } /* * call-seq: * hsh.each_pair {| key_value_array | block } -> hsh * * Calls block once for each key in hsh, passing the key * and value as parameters. * * h = { "a" => 100, "b" => 200 } * h.each_pair {|key, value| puts "#{key} is #{value}" } * * produces: * * a is 100 * b is 200 * */ static VALUE rb_hash_each_pair(hash) VALUE hash; { rb_hash_foreach(hash, each_pair_i, 0); return hash; } static int each_i(key, value) VALUE key, value; { if (key == Qundef) return ST_CONTINUE; rb_yield(rb_assoc_new(key, value)); return ST_CONTINUE; } /* * call-seq: * hsh.each {| key, value | block } -> hsh * * Calls block once for each key in hsh, passing the key * and value to the block as a two-element array. Because of the assignment * semantics of block parameters, these elements will be split out if the * block has two formal parameters. Also see Hash.each_pair, which * will be marginally more efficient for blocks with two parameters. * * h = { "a" => 100, "b" => 200 } * h.each {|key, value| puts "#{key} is #{value}" } * * produces: * * a is 100 * b is 200 * */ static VALUE rb_hash_each(hash) VALUE hash; { rb_hash_foreach(hash, each_i, 0); return hash; } static int to_a_i(key, value, ary) VALUE key, value, ary; { if (key == Qundef) return ST_CONTINUE; rb_ary_push(ary, rb_assoc_new(key, value)); return ST_CONTINUE; } /* * call-seq: * hsh.to_a -> array * * Converts hsh to a nested array of [ key, * value ] arrays. * * h = { "c" => 300, "a" => 100, "d" => 400, "c" => 300 } * h.to_a #=> [["a", 100], ["c", 300], ["d", 400]] */ static VALUE rb_hash_to_a(hash) VALUE hash; { VALUE ary; ary = rb_ary_new(); rb_hash_foreach(hash, to_a_i, ary); if (OBJ_TAINTED(hash)) OBJ_TAINT(ary); return ary; } /* * call-seq: * hsh.sort => array * hsh.sort {| a, b | block } => array * * Converts hsh to a nested array of [ key, * value ] arrays and sorts it, using * Array#sort. * * h = { "a" => 20, "b" => 30, "c" => 10 } * h.sort #=> [["a", 20], ["b", 30], ["c", 10]] * h.sort {|a,b| a[1]<=>b[1]} #=> [["c", 10], ["a", 20], ["b", 30]] * */ static VALUE rb_hash_sort(hash) VALUE hash; { VALUE entries = rb_hash_to_a(hash); rb_ary_sort_bang(entries); return entries; } static int inspect_i(key, value, str) VALUE key, value, str; { VALUE str2; if (key == Qundef) return ST_CONTINUE; if (RSTRING(str)->len > 1) { rb_str_cat2(str, ", "); } str2 = rb_inspect(key); rb_str_buf_append(str, str2); OBJ_INFECT(str, str2); rb_str_buf_cat2(str, "=>"); str2 = rb_inspect(value); rb_str_buf_append(str, str2); OBJ_INFECT(str, str2); return ST_CONTINUE; } static VALUE inspect_hash(hash) VALUE hash; { VALUE str; str = rb_str_buf_new2("{"); rb_hash_foreach(hash, inspect_i, str); rb_str_buf_cat2(str, "}"); OBJ_INFECT(str, hash); return str; } /* * call-seq: * hsh.inspect => string * * Return the contents of this hash as a string. */ static VALUE rb_hash_inspect(hash) VALUE hash; { if (RHASH(hash)->tbl == 0 || RHASH(hash)->tbl->num_entries == 0) return rb_str_new2("{}"); if (rb_inspecting_p(hash)) return rb_str_new2("{...}"); return rb_protect_inspect(inspect_hash, hash, 0); } static VALUE to_s_hash(hash) VALUE hash; { return rb_ary_to_s(rb_hash_to_a(hash)); } /* * call-seq: * hsh.to_s => string * * Converts hsh to a string by converting the hash to an array * of [ key, value ] pairs and then * converting that array to a string using Array#join with * the default separator. * * h = { "c" => 300, "a" => 100, "d" => 400, "c" => 300 } * h.to_s #=> "a100c300d400" */ static VALUE rb_hash_to_s(hash) VALUE hash; { if (rb_inspecting_p(hash)) return rb_str_new2("{...}"); return rb_protect_inspect(to_s_hash, hash, 0); } /* * call-seq: * hsh.to_hash => hsh * * Returns self. */ static VALUE rb_hash_to_hash(hash) VALUE hash; { return hash; } static int keys_i(key, value, ary) VALUE key, value, ary; { if (key == Qundef) return ST_CONTINUE; rb_ary_push(ary, key); return ST_CONTINUE; } /* * call-seq: * hsh.keys => array * * Returns a new array populated with the keys from this hash. See also * Hash#values. * * h = { "a" => 100, "b" => 200, "c" => 300, "d" => 400 } * h.keys #=> ["a", "b", "c", "d"] * */ static VALUE rb_hash_keys(hash) VALUE hash; { VALUE ary; ary = rb_ary_new(); rb_hash_foreach(hash, keys_i, ary); return ary; } static int values_i(key, value, ary) VALUE key, value, ary; { if (key == Qundef) return ST_CONTINUE; rb_ary_push(ary, value); return ST_CONTINUE; } /* * call-seq: * hsh.values => array * * Returns a new array populated with the values from hsh. See * also Hash#keys. * * h = { "a" => 100, "b" => 200, "c" => 300 } * h.values #=> [100, 200, 300] * */ static VALUE rb_hash_values(hash) VALUE hash; { VALUE ary; ary = rb_ary_new(); rb_hash_foreach(hash, values_i, ary); return ary; } /* * call-seq: * hsh.has_key?(key) => true or false * hsh.include?(key) => true or false * hsh.key?(key) => true or false * hsh.member?(key) => true or false * * Returns true if the given key is present in hsh. * * h = { "a" => 100, "b" => 200 } * h.has_key?("a") #=> true * h.has_key?("z") #=> false * */ static VALUE rb_hash_has_key(hash, key) VALUE hash; VALUE key; { if (st_lookup(RHASH(hash)->tbl, key, 0)) { return Qtrue; } return Qfalse; } static int rb_hash_search_value(key, value, data) VALUE key, value, *data; { if (key == Qundef) return ST_CONTINUE; if (rb_equal(value, data[1])) { data[0] = Qtrue; return ST_STOP; } return ST_CONTINUE; } /* * call-seq: * hsh.has_value?(value) => true or false * hsh.value?(value) => true or false * * Returns true if the given value is present for some key * in hsh. * * h = { "a" => 100, "b" => 200 } * h.has_value?(100) #=> true * h.has_value?(999) #=> false */ static VALUE rb_hash_has_value(hash, val) VALUE hash; VALUE val; { VALUE data[2]; data[0] = Qfalse; data[1] = val; rb_hash_foreach(hash, rb_hash_search_value, (st_data_t)data); return data[0]; } struct equal_data { int result; st_table *tbl; }; static int equal_i(key, val1, data) VALUE key, val1; struct equal_data *data; { VALUE val2; if (key == Qundef) return ST_CONTINUE; if (!st_lookup(data->tbl, key, &val2)) { data->result = Qfalse; return ST_STOP; } if (!rb_equal(val1, val2)) { data->result = Qfalse; return ST_STOP; } return ST_CONTINUE; } static VALUE hash_equal(hash1, hash2, eql) VALUE hash1, hash2; int eql; /* compare default value if true */ { struct equal_data data; if (hash1 == hash2) return Qtrue; if (TYPE(hash2) != T_HASH) { if (!rb_respond_to(hash2, rb_intern("to_hash"))) { return Qfalse; } return rb_equal(hash2, hash1); } if (RHASH(hash1)->tbl->num_entries != RHASH(hash2)->tbl->num_entries) return Qfalse; if (eql) { if (!(rb_equal(RHASH(hash1)->ifnone, RHASH(hash2)->ifnone) && FL_TEST(hash1, HASH_PROC_DEFAULT) == FL_TEST(hash2, HASH_PROC_DEFAULT))) return Qfalse; } data.tbl = RHASH(hash2)->tbl; data.result = Qtrue; rb_hash_foreach(hash1, equal_i, (st_data_t)&data); return data.result; } /* * call-seq: * hsh == other_hash => true or false * * Equality---Two hashes are equal if they each contain the same number * of keys and if each key-value pair is equal to (according to * Object#==) the corresponding elements in the other * hash. * * h1 = { "a" => 1, "c" => 2 } * h2 = { 7 => 35, "c" => 2, "a" => 1 } * h3 = { "a" => 1, "c" => 2, 7 => 35 } * h4 = { "a" => 1, "d" => 2, "f" => 35 } * h1 == h2 #=> false * h2 == h3 #=> true * h3 == h4 #=> false * */ static VALUE rb_hash_equal(hash1, hash2) VALUE hash1, hash2; { return hash_equal(hash1, hash2, Qfalse); } static int rb_hash_invert_i(key, value, hash) VALUE key, value; VALUE hash; { if (key == Qundef) return ST_CONTINUE; rb_hash_aset(hash, value, key); return ST_CONTINUE; } /* * call-seq: * hsh.invert -> aHash * * Returns a new hash created by using hsh's values as keys, and * the keys as values. * * h = { "n" => 100, "m" => 100, "y" => 300, "d" => 200, "a" => 0 } * h.invert #=> {0=>"a", 100=>"n", 200=>"d", 300=>"y"} * */ static VALUE rb_hash_invert(hash) VALUE hash; { VALUE h = rb_hash_new(); rb_hash_foreach(hash, rb_hash_invert_i, h); return h; } static int rb_hash_update_i(key, value, hash) VALUE key, value; VALUE hash; { if (key == Qundef) return ST_CONTINUE; rb_hash_aset(hash, key, value); return ST_CONTINUE; } static int rb_hash_update_block_i(key, value, hash) VALUE key, value; VALUE hash; { if (key == Qundef) return ST_CONTINUE; if (rb_hash_has_key(hash, key)) { value = rb_yield_values(3, key, rb_hash_aref(hash, key), value); } rb_hash_aset(hash, key, value); return ST_CONTINUE; } /* * call-seq: * hsh.merge!(other_hash) => hsh * hsh.update(other_hash) => hsh * hsh.merge!(other_hash){|key, oldval, newval| block} => hsh * hsh.update(other_hash){|key, oldval, newval| block} => hsh * * Adds the contents of other_hash to hsh, overwriting * entries with duplicate keys with those from other_hash. * * h1 = { "a" => 100, "b" => 200 } * h2 = { "b" => 254, "c" => 300 } * h1.merge!(h2) #=> {"a"=>100, "b"=>254, "c"=>300} */ static VALUE rb_hash_update(hash1, hash2) VALUE hash1, hash2; { hash2 = to_hash(hash2); if (rb_block_given_p()) { rb_hash_foreach(hash2, rb_hash_update_block_i, hash1); } else { rb_hash_foreach(hash2, rb_hash_update_i, hash1); } return hash1; } /* * call-seq: * hsh.merge(other_hash) -> a_hash * hsh.merge(other_hash){|key, oldval, newval| block} -> a_hash * * Returns a new hash containing the contents of other_hash and * the contents of hsh, overwriting entries in hsh with * duplicate keys with those from other_hash. * * h1 = { "a" => 100, "b" => 200 } * h2 = { "b" => 254, "c" => 300 } * h1.merge(h2) #=> {"a"=>100, "b"=>254, "c"=>300} * h1 #=> {"a"=>100, "b"=>200} * */ static VALUE rb_hash_merge(hash1, hash2) VALUE hash1, hash2; { return rb_hash_update(rb_obj_dup(hash1), hash2); } static int path_tainted = -1; static char **origenviron; #ifdef _WIN32 #define GET_ENVIRON(e) (e = rb_w32_get_environ()) #define FREE_ENVIRON(e) rb_w32_free_environ(e) static char **my_environ; #undef environ #define environ my_environ #elif defined(__APPLE__) #undef environ #define environ (*_NSGetEnviron()) #define GET_ENVIRON(e) (e) #define FREE_ENVIRON(e) #else extern char **environ; #define GET_ENVIRON(e) (e) #define FREE_ENVIRON(e) #endif static VALUE env_str_new(ptr, len) const char *ptr; long len; { VALUE str = rb_tainted_str_new(ptr, len); rb_obj_freeze(str); return str; } static VALUE env_str_new2(ptr) const char *ptr; { if (!ptr) return Qnil; return env_str_new(ptr, strlen(ptr)); } static VALUE env_delete(obj, name) VALUE obj, name; { char *nam, *val; rb_secure(4); SafeStringValue(name); nam = RSTRING(name)->ptr; if (strlen(nam) != RSTRING(name)->len) { rb_raise(rb_eArgError, "bad environment variable name"); } val = getenv(nam); if (val) { VALUE value = env_str_new2(val); ruby_setenv(nam, 0); #ifdef ENV_IGNORECASE if (strcasecmp(nam, PATH_ENV) == 0) #else if (strcmp(nam, PATH_ENV) == 0) #endif { path_tainted = 0; } return value; } return Qnil; } static VALUE env_delete_m(obj, name) VALUE obj, name; { VALUE val; val = env_delete(obj, name); if (NIL_P(val) && rb_block_given_p()) rb_yield(name); return val; } static VALUE rb_f_getenv(obj, name) VALUE obj, name; { char *nam, *env; StringValue(name); nam = RSTRING(name)->ptr; if (strlen(nam) != RSTRING(name)->len) { rb_raise(rb_eArgError, "bad environment variable name"); } env = getenv(nam); if (env) { #ifdef ENV_IGNORECASE if (strcasecmp(nam, PATH_ENV) == 0 && !rb_env_path_tainted()) #else if (strcmp(nam, PATH_ENV) == 0 && !rb_env_path_tainted()) #endif { VALUE str = rb_str_new2(env); rb_obj_freeze(str); return str; } return env_str_new2(env); } return Qnil; } static VALUE env_fetch(argc, argv) int argc; VALUE *argv; { VALUE key, if_none; long block_given; char *nam, *env; rb_scan_args(argc, argv, "11", &key, &if_none); block_given = rb_block_given_p(); if (block_given && argc == 2) { rb_warn("block supersedes default value argument"); } StringValue(key); nam = RSTRING(key)->ptr; if (strlen(nam) != RSTRING(key)->len) { rb_raise(rb_eArgError, "bad environment variable name"); } env = getenv(nam); if (!env) { if (block_given) return rb_yield(key); if (argc == 1) { rb_raise(rb_eIndexError, "key not found"); } return if_none; } #ifdef ENV_IGNORECASE if (strcasecmp(nam, PATH_ENV) == 0 && !rb_env_path_tainted()) #else if (strcmp(nam, PATH_ENV) == 0 && !rb_env_path_tainted()) #endif return rb_str_new2(env); return env_str_new2(env); } static void path_tainted_p(path) char *path; { path_tainted = rb_path_check(path)?0:1; } int rb_env_path_tainted() { if (path_tainted < 0) { path_tainted_p(getenv(PATH_ENV)); } return path_tainted; } static int envix(nam) const char *nam; { register int i, len = strlen(nam); char **env; env = GET_ENVIRON(environ); for (i = 0; env[i]; i++) { if ( #ifdef ENV_IGNORECASE strncasecmp(env[i],nam,len) == 0 #else memcmp(env[i],nam,len) == 0 #endif && env[i][len] == '=') break; /* memcmp must come first to avoid */ } /* potential SEGV's */ FREE_ENVIRON(environ); return i; } void ruby_setenv(name, value) const char *name; const char *value; { #if defined(_WIN32) /* The sane way to deal with the environment. * Has these advantages over putenv() & co.: * * enables us to store a truly empty value in the * environment (like in UNIX). * * we don't have to deal with RTL globals, bugs and leaks. * * Much faster. * Why you may want to enable USE_WIN32_RTL_ENV: * * environ[] and RTL functions will not reflect changes, * which might be an issue if extensions want to access * the env. via RTL. This cuts both ways, since RTL will * not see changes made by extensions that call the Win32 * functions directly, either. * GSAR 97-06-07 * * REMARK: USE_WIN32_RTL_ENV is already obsoleted since we don't use * RTL's environ global variable directly yet. */ SetEnvironmentVariable(name,value); #elif defined(HAVE_SETENV) && defined(HAVE_UNSETENV) #undef setenv #undef unsetenv if (value) setenv(name,value,1); else unsetenv(name); #else /* WIN32 */ size_t len; int i=envix(name); /* where does it go? */ if (environ == origenviron) { /* need we copy environment? */ int j; int max; char **tmpenv; for (max = i; environ[max]; max++) ; tmpenv = ALLOC_N(char*, max+2); for (j=0; j= 4) { rb_raise(rb_eSecurityError, "can't change environment variable"); } if (NIL_P(val)) { env_delete(obj, nm); return Qnil; } StringValue(nm); StringValue(val); name = RSTRING(nm)->ptr; value = RSTRING(val)->ptr; if (strlen(name) != RSTRING(nm)->len) rb_raise(rb_eArgError, "bad environment variable name"); if (strlen(value) != RSTRING(val)->len) rb_raise(rb_eArgError, "bad environment variable value"); ruby_setenv(name, value); #ifdef ENV_IGNORECASE if (strcasecmp(name, PATH_ENV) == 0) { #else if (strcmp(name, PATH_ENV) == 0) { #endif if (OBJ_TAINTED(val)) { /* already tainted, no check */ path_tainted = 1; return val; } else { path_tainted_p(value); } } return val; } static VALUE env_keys() { char **env; VALUE ary = rb_ary_new(); env = GET_ENVIRON(environ); while (*env) { char *s = strchr(*env, '='); if (s) { rb_ary_push(ary, env_str_new(*env, s-*env)); } env++; } FREE_ENVIRON(environ); return ary; } static VALUE env_each_key(ehash) VALUE ehash; { VALUE keys = env_keys(); long i; for (i=0; ilen; i++) { rb_yield(RARRAY(keys)->ptr[i]); } return ehash; } static VALUE env_values() { char **env; VALUE ary = rb_ary_new(); env = GET_ENVIRON(environ); while (*env) { char *s = strchr(*env, '='); if (s) { rb_ary_push(ary, env_str_new2(s+1)); } env++; } FREE_ENVIRON(environ); return ary; } static VALUE env_each_value(ehash) VALUE ehash; { VALUE values = env_values(); long i; for (i=0; ilen; i++) { rb_yield(RARRAY(values)->ptr[i]); } return ehash; } static VALUE env_each_i(ehash, values) VALUE ehash; int values; { char **env; VALUE ary = rb_ary_new(); long i; env = GET_ENVIRON(environ); while (*env) { char *s = strchr(*env, '='); if (s) { rb_ary_push(ary, env_str_new(*env, s-*env)); rb_ary_push(ary, env_str_new2(s+1)); } env++; } FREE_ENVIRON(environ); for (i=0; ilen; i+=2) { if (values) { rb_yield_values(2, RARRAY(ary)->ptr[i], RARRAY(ary)->ptr[i+1]); } else { rb_yield(rb_assoc_new(RARRAY(ary)->ptr[i], RARRAY(ary)->ptr[i+1])); } } return ehash; } static VALUE env_each(ehash) VALUE ehash; { return env_each_i(ehash, Qfalse); } static VALUE env_each_pair(ehash) VALUE ehash; { return env_each_i(ehash, Qtrue); } static VALUE env_reject_bang() { volatile VALUE keys; long i; int del = 0; rb_secure(4); keys = env_keys(); for (i=0; ilen; i++) { VALUE val = rb_f_getenv(Qnil, RARRAY(keys)->ptr[i]); if (!NIL_P(val)) { if (RTEST(rb_yield_values(2, RARRAY(keys)->ptr[i], val))) { FL_UNSET(RARRAY(keys)->ptr[i], FL_TAINT); env_delete(Qnil, RARRAY(keys)->ptr[i]); del++; } } } if (del == 0) return Qnil; return envtbl; } static VALUE env_delete_if() { env_reject_bang(); return envtbl; } static VALUE env_values_at(argc, argv) int argc; VALUE *argv; { VALUE result = rb_ary_new(); long i; for (i=0; i 0) { rb_raise(rb_eArgError, "wrong number of arguments (%d for 0)", argc); } result = rb_ary_new(); env = GET_ENVIRON(environ); while (*env) { char *s = strchr(*env, '='); if (s) { VALUE k = env_str_new(*env, s-*env); VALUE v = env_str_new2(s+1); if (RTEST(rb_yield_values(2, k, v))) { rb_ary_push(result, rb_assoc_new(k, v)); } } env++; } FREE_ENVIRON(environ); return result; } static VALUE env_clear() { volatile VALUE keys; long i; rb_secure(4); keys = env_keys(); for (i=0; ilen; i++) { VALUE val = rb_f_getenv(Qnil, RARRAY(keys)->ptr[i]); if (!NIL_P(val)) { env_delete(Qnil, RARRAY(keys)->ptr[i]); } } return envtbl; } static VALUE env_to_s() { return rb_str_new2("ENV"); } static VALUE env_inspect() { char **env; VALUE str = rb_str_buf_new2("{"); VALUE i; env = GET_ENVIRON(environ); while (*env) { char *s = strchr(*env, '='); if (env != environ) { rb_str_buf_cat2(str, ", "); } if (s) { rb_str_buf_cat2(str, "\""); rb_str_buf_cat(str, *env, s-*env); rb_str_buf_cat2(str, "\"=>"); i = rb_inspect(rb_str_new2(s+1)); rb_str_buf_append(str, i); } env++; } FREE_ENVIRON(environ); rb_str_buf_cat2(str, "}"); OBJ_TAINT(str); return str; } static VALUE env_to_a() { char **env; VALUE ary = rb_ary_new(); env = GET_ENVIRON(environ); while (*env) { char *s = strchr(*env, '='); if (s) { rb_ary_push(ary, rb_assoc_new(env_str_new(*env, s-*env), env_str_new2(s+1))); } env++; } FREE_ENVIRON(environ); return ary; } static VALUE env_none() { return Qnil; } static VALUE env_size() { int i; char **env; env = GET_ENVIRON(environ); for(i=0; env[i]; i++) ; FREE_ENVIRON(environ); return INT2FIX(i); } static VALUE env_empty_p() { char **env; env = GET_ENVIRON(environ); if (env[0] == 0) { FREE_ENVIRON(environ); return Qtrue; } FREE_ENVIRON(environ); return Qfalse; } static VALUE env_has_key(env, key) VALUE env, key; { char *s; s = StringValuePtr(key); if (strlen(s) != RSTRING(key)->len) rb_raise(rb_eArgError, "bad environment variable name"); if (getenv(s)) return Qtrue; return Qfalse; } static VALUE env_has_value(dmy, value) VALUE dmy, value; { char **env; if (TYPE(value) != T_STRING) return Qfalse; env = GET_ENVIRON(environ); while (*env) { char *s = strchr(*env, '='); if (s++) { long len = strlen(s); if (RSTRING(value)->len == len && strncmp(s, RSTRING(value)->ptr, len) == 0) { FREE_ENVIRON(environ); return Qtrue; } } env++; } FREE_ENVIRON(environ); return Qfalse; } static VALUE env_index(dmy, value) VALUE dmy, value; { char **env; VALUE str; StringValue(value); env = GET_ENVIRON(environ); while (*env) { char *s = strchr(*env, '='); if (s++) { long len = strlen(s); if (RSTRING(value)->len == len && strncmp(s, RSTRING(value)->ptr, len) == 0) { str = env_str_new(*env, s-*env-1); FREE_ENVIRON(environ); return str; } } env++; } FREE_ENVIRON(environ); return Qnil; } static VALUE env_indexes(argc, argv) int argc; VALUE *argv; { int i; VALUE indexes = rb_ary_new2(argc); rb_warn("ENV.%s is deprecated; use ENV.values_at", rb_id2name(rb_frame_last_func())); for (i=0;iptr[i] = Qnil; } else { RARRAY(indexes)->ptr[i] = env_str_new2(getenv(RSTRING(tmp)->ptr)); } RARRAY(indexes)->len = i+1; } return indexes; } static VALUE env_to_hash() { char **env; VALUE hash = rb_hash_new(); env = GET_ENVIRON(environ); while (*env) { char *s = strchr(*env, '='); if (s) { rb_hash_aset(hash, env_str_new(*env, s-*env), env_str_new2(s+1)); } env++; } FREE_ENVIRON(environ); return hash; } static VALUE env_reject() { return rb_hash_delete_if(env_to_hash()); } static VALUE env_shift() { char **env; env = GET_ENVIRON(environ); if (*env) { char *s = strchr(*env, '='); if (s) { VALUE key = env_str_new(*env, s-*env); VALUE val = env_str_new2(getenv(RSTRING(key)->ptr)); env_delete(Qnil, key); return rb_assoc_new(key, val); } } FREE_ENVIRON(environ); return Qnil; } static VALUE env_invert() { return rb_hash_invert(env_to_hash()); } static int env_replace_i(key, val, keys) VALUE key, val, keys; { if (key != Qundef) { env_aset(Qnil, key, val); if (rb_ary_includes(keys, key)) { rb_ary_delete(keys, key); } } return ST_CONTINUE; } static VALUE env_replace(env, hash) VALUE env, hash; { volatile VALUE keys = env_keys(); long i; if (env == hash) return env; hash = to_hash(hash); rb_hash_foreach(hash, env_replace_i, keys); for (i=0; ilen; i++) { env_delete(env, RARRAY(keys)->ptr[i]); } return env; } static int env_update_i(key, val) VALUE key, val; { if (key != Qundef) { if (rb_block_given_p()) { val = rb_yield_values(3, key, rb_f_getenv(Qnil, key), val); } env_aset(Qnil, key, val); } return ST_CONTINUE; } static VALUE env_update(env, hash) VALUE env, hash; { if (env == hash) return env; hash = to_hash(hash); rb_hash_foreach(hash, env_update_i, 0); return env; } /* * A Hash is a collection of key-value pairs. It is * similar to an Array, except that indexing is done via * arbitrary keys of any object type, not an integer index. The order * in which you traverse a hash by either key or value may seem * arbitrary, and will generally not be in the insertion order. * * Hashes have a default value that is returned when accessing * keys that do not exist in the hash. By default, that value is * nil. * * Hash uses key.eql? to test keys for equality. * If you need to use instances of your own classes as keys in a Hash, * it is recommended that you define both the eql? and hash * methods. The hash method must have the property that * a.eql?(b) implies a.hash == b.hash. * * class MyClass * attr_reader :str * def initialize(str) * @str = str * end * def eql?(o) * o.is_a?(MyClass) && str == o.str * end * def hash * @str.hash * end * end * * a = MyClass.new("some string") * b = MyClass.new("some string") * a.eql? b #=> true * * h = {} * * h[a] = 1 * h[a] #=> 1 * h[b] #=> 1 * * h[b] = 2 * h[a] #=> 2 * h[b] #=> 2 */ void Init_Hash() { id_hash = rb_intern("hash"); id_call = rb_intern("call"); id_default = rb_intern("default"); rb_cHash = rb_define_class("Hash", rb_cObject); rb_include_module(rb_cHash, rb_mEnumerable); rb_define_alloc_func(rb_cHash, hash_alloc); rb_define_singleton_method(rb_cHash, "[]", rb_hash_s_create, -1); rb_define_method(rb_cHash,"initialize", rb_hash_initialize, -1); rb_define_method(rb_cHash,"initialize_copy", rb_hash_replace, 1); rb_define_method(rb_cHash,"rehash", rb_hash_rehash, 0); rb_define_method(rb_cHash,"to_hash", rb_hash_to_hash, 0); rb_define_method(rb_cHash,"to_a", rb_hash_to_a, 0); rb_define_method(rb_cHash,"to_s", rb_hash_to_s, 0); rb_define_method(rb_cHash,"inspect", rb_hash_inspect, 0); rb_define_method(rb_cHash,"==", rb_hash_equal, 1); rb_define_method(rb_cHash,"[]", rb_hash_aref, 1); rb_define_method(rb_cHash,"fetch", rb_hash_fetch, -1); rb_define_method(rb_cHash,"[]=", rb_hash_aset, 2); rb_define_method(rb_cHash,"store", rb_hash_aset, 2); rb_define_method(rb_cHash,"default", rb_hash_default, -1); rb_define_method(rb_cHash,"default=", rb_hash_set_default, 1); rb_define_method(rb_cHash,"default_proc", rb_hash_default_proc, 0); rb_define_method(rb_cHash,"index", rb_hash_index, 1); rb_define_method(rb_cHash,"indexes", rb_hash_indexes, -1); rb_define_method(rb_cHash,"indices", rb_hash_indexes, -1); rb_define_method(rb_cHash,"size", rb_hash_size, 0); rb_define_method(rb_cHash,"length", rb_hash_size, 0); rb_define_method(rb_cHash,"empty?", rb_hash_empty_p, 0); rb_define_method(rb_cHash,"each", rb_hash_each, 0); rb_define_method(rb_cHash,"each_value", rb_hash_each_value, 0); rb_define_method(rb_cHash,"each_key", rb_hash_each_key, 0); rb_define_method(rb_cHash,"each_pair", rb_hash_each_pair, 0); rb_define_method(rb_cHash,"sort", rb_hash_sort, 0); rb_define_method(rb_cHash,"keys", rb_hash_keys, 0); rb_define_method(rb_cHash,"values", rb_hash_values, 0); rb_define_method(rb_cHash,"values_at", rb_hash_values_at, -1); rb_define_method(rb_cHash,"shift", rb_hash_shift, 0); rb_define_method(rb_cHash,"delete", rb_hash_delete, 1); rb_define_method(rb_cHash,"delete_if", rb_hash_delete_if, 0); rb_define_method(rb_cHash,"select", rb_hash_select, -1); rb_define_method(rb_cHash,"reject", rb_hash_reject, 0); rb_define_method(rb_cHash,"reject!", rb_hash_reject_bang, 0); rb_define_method(rb_cHash,"clear", rb_hash_clear, 0); rb_define_method(rb_cHash,"invert", rb_hash_invert, 0); rb_define_method(rb_cHash,"update", rb_hash_update, 1); rb_define_method(rb_cHash,"replace", rb_hash_replace, 1); rb_define_method(rb_cHash,"merge!", rb_hash_update, 1); rb_define_method(rb_cHash,"merge", rb_hash_merge, 1); rb_define_method(rb_cHash,"include?", rb_hash_has_key, 1); rb_define_method(rb_cHash,"member?", rb_hash_has_key, 1); rb_define_method(rb_cHash,"has_key?", rb_hash_has_key, 1); rb_define_method(rb_cHash,"has_value?", rb_hash_has_value, 1); rb_define_method(rb_cHash,"key?", rb_hash_has_key, 1); rb_define_method(rb_cHash,"value?", rb_hash_has_value, 1); #ifndef __MACOS__ /* environment variables nothing on MacOS. */ origenviron = environ; envtbl = rb_obj_alloc(rb_cObject); rb_extend_object(envtbl, rb_mEnumerable); rb_define_singleton_method(envtbl,"[]", rb_f_getenv, 1); rb_define_singleton_method(envtbl,"fetch", env_fetch, -1); rb_define_singleton_method(envtbl,"[]=", env_aset, 2); rb_define_singleton_method(envtbl,"store", env_aset, 2); rb_define_singleton_method(envtbl,"each", env_each, 0); rb_define_singleton_method(envtbl,"each_pair", env_each_pair, 0); rb_define_singleton_method(envtbl,"each_key", env_each_key, 0); rb_define_singleton_method(envtbl,"each_value", env_each_value, 0); rb_define_singleton_method(envtbl,"delete", env_delete_m, 1); rb_define_singleton_method(envtbl,"delete_if", env_delete_if, 0); rb_define_singleton_method(envtbl,"clear", env_clear, 0); rb_define_singleton_method(envtbl,"reject", env_reject, 0); rb_define_singleton_method(envtbl,"reject!", env_reject_bang, 0); rb_define_singleton_method(envtbl,"select", env_select, -1); rb_define_singleton_method(envtbl,"shift", env_shift, 0); rb_define_singleton_method(envtbl,"invert", env_invert, 0); rb_define_singleton_method(envtbl,"replace", env_replace, 1); rb_define_singleton_method(envtbl,"update", env_update, 1); rb_define_singleton_method(envtbl,"inspect", env_inspect, 0); rb_define_singleton_method(envtbl,"rehash", env_none, 0); rb_define_singleton_method(envtbl,"to_a", env_to_a, 0); rb_define_singleton_method(envtbl,"to_s", env_to_s, 0); rb_define_singleton_method(envtbl,"index", env_index, 1); rb_define_singleton_method(envtbl,"indexes", env_indexes, -1); rb_define_singleton_method(envtbl,"indices", env_indexes, -1); rb_define_singleton_method(envtbl,"size", env_size, 0); rb_define_singleton_method(envtbl,"length", env_size, 0); rb_define_singleton_method(envtbl,"empty?", env_empty_p, 0); rb_define_singleton_method(envtbl,"keys", env_keys, 0); rb_define_singleton_method(envtbl,"values", env_values, 0); rb_define_singleton_method(envtbl,"values_at", env_values_at, -1); rb_define_singleton_method(envtbl,"include?", env_has_key, 1); rb_define_singleton_method(envtbl,"member?", env_has_key, 1); rb_define_singleton_method(envtbl,"has_key?", env_has_key, 1); rb_define_singleton_method(envtbl,"has_value?", env_has_value, 1); rb_define_singleton_method(envtbl,"key?", env_has_key, 1); rb_define_singleton_method(envtbl,"value?", env_has_value, 1); rb_define_singleton_method(envtbl,"to_hash", env_to_hash, 0); rb_define_global_const("ENV", envtbl); #else /* __MACOS__ */ envtbl = rb_hash_s_new(0, NULL, rb_cHash); rb_define_global_const("ENV", envtbl); #endif /* ifndef __MACOS__ environment variables nothing on MacOS. */ } /********************************************************************** inits.c - $Author: shyouhei $ $Date: 2007-02-13 00:01:19 +0100 (Tue, 13 Feb 2007) $ created at: Tue Dec 28 16:01:58 JST 1993 Copyright (C) 1993-2003 Yukihiro Matsumoto **********************************************************************/ #include "ruby.h" void Init_Array _((void)); void Init_Bignum _((void)); void Init_Binding _((void)); void Init_Comparable _((void)); void Init_Dir _((void)); void Init_Enumerable _((void)); void Init_Exception _((void)); void Init_syserr _((void)); void Init_eval _((void)); void Init_load _((void)); void Init_Proc _((void)); void Init_Thread _((void)); void Init_File _((void)); void Init_GC _((void)); void Init_Hash _((void)); void Init_IO _((void)); void Init_Math _((void)); void Init_marshal _((void)); void Init_Numeric _((void)); void Init_Object _((void)); void Init_pack _((void)); void Init_Precision _((void)); void Init_sym _((void)); void Init_process _((void)); void Init_Random _((void)); void Init_Range _((void)); void Init_Regexp _((void)); void Init_signal _((void)); void Init_String _((void)); void Init_Struct _((void)); void Init_Time _((void)); void Init_var_tables _((void)); void Init_version _((void)); void rb_call_inits() { Init_sym(); Init_var_tables(); Init_Object(); Init_Comparable(); Init_Enumerable(); Init_Precision(); Init_eval(); Init_String(); Init_Exception(); Init_Thread(); Init_Numeric(); Init_Bignum(); Init_syserr(); Init_Array(); Init_Hash(); Init_Struct(); Init_Regexp(); Init_pack(); Init_Range(); Init_IO(); Init_Dir(); Init_Time(); Init_Random(); Init_signal(); Init_process(); Init_load(); Init_Proc(); Init_Binding(); Init_Math(); Init_GC(); Init_marshal(); Init_version(); } /********************************************************************** io.c - $Author: shyouhei $ $Date: 2009-03-09 01:52:15 +0100 (Mon, 09 Mar 2009) $ created at: Fri Oct 15 18:08:59 JST 1993 Copyright (C) 1993-2003 Yukihiro Matsumoto Copyright (C) 2000 Network Applied Communication Laboratory, Inc. Copyright (C) 2000 Information-technology Promotion Agency, Japan **********************************************************************/ #if defined(__VMS) #define _XOPEN_SOURCE #define _POSIX_C_SOURCE 2 #endif #include "ruby.h" #include "rubyio.h" #include "rubysig.h" #include "env.h" #include #include #if defined(MSDOS) || defined(__BOW__) || defined(__CYGWIN__) || defined(_WIN32) || defined(__human68k__) || defined(__EMX__) || defined(__BEOS__) # define NO_SAFE_RENAME #endif #if defined(MSDOS) || defined(__CYGWIN__) || defined(_WIN32) # define NO_LONG_FNAME #endif #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) || defined(sun) || defined(_nec_ews) # define USE_SETVBUF #endif #ifdef __QNXNTO__ #include "unix.h" #endif #include #if defined(HAVE_SYS_IOCTL_H) && !defined(DJGPP) && !defined(_WIN32) && !defined(__human68k__) #include #endif #if defined(HAVE_FCNTL_H) || defined(_WIN32) #include #elif defined(HAVE_SYS_FCNTL_H) #include #endif #if !HAVE_OFF_T && !defined(off_t) # define off_t long #endif #if !HAVE_FSEEKO && !defined(fseeko) # define fseeko fseek #endif #if !HAVE_FTELLO && !defined(ftello) # define ftello ftell #endif #include /* EMX has sys/param.h, but.. */ #if defined(HAVE_SYS_PARAM_H) && !(defined(__EMX__) || defined(__HIUX_MPP__)) # include #endif #if !defined NOFILE # define NOFILE 64 #endif #ifdef HAVE_UNISTD_H #ifdef HAVE_SYSCALL_H #include #elif defined HAVE_SYS_SYSCALL_H #include #endif #include #endif extern void Init_File _((void)); #ifdef __BEOS__ # ifndef NOFILE # define NOFILE (OPEN_MAX) # endif #include #endif #include "util.h" #ifndef O_ACCMODE #define O_ACCMODE (O_RDONLY | O_WRONLY | O_RDWR) #endif #if SIZEOF_OFF_T > SIZEOF_LONG && !defined(HAVE_LONG_LONG) # error off_t is bigger than long, but you have no long long... #endif #ifndef PIPE_BUF # ifdef _POSIX_PIPE_BUF # define PIPE_BUF _POSIX_PIPE_BUF # else # define PIPE_BUF 512 /* is this ok? */ # endif #endif VALUE rb_cIO; VALUE rb_eEOFError; VALUE rb_eIOError; VALUE rb_stdin, rb_stdout, rb_stderr; VALUE rb_deferr; /* rescue VIM plugin */ static VALUE orig_stdout, orig_stderr; VALUE rb_output_fs; VALUE rb_rs; VALUE rb_output_rs; VALUE rb_default_rs; static VALUE argf; static ID id_write, id_read, id_getc; extern char *ruby_inplace_mode; struct timeval rb_time_interval _((VALUE)); static VALUE filename, current_file; static int gets_lineno; static int init_p = 0, next_p = 0; static VALUE lineno = INT2FIX(0); #ifdef _STDIO_USES_IOSTREAM /* GNU libc */ # ifdef _IO_fpos_t # define READ_DATA_PENDING(fp) ((fp)->_IO_read_ptr != (fp)->_IO_read_end) # define READ_DATA_PENDING_COUNT(fp) ((fp)->_IO_read_end - (fp)->_IO_read_ptr) # define READ_DATA_PENDING_PTR(fp) ((fp)->_IO_read_ptr) # else # define READ_DATA_PENDING(fp) ((fp)->_gptr < (fp)->_egptr) # define READ_DATA_PENDING_COUNT(fp) ((fp)->_egptr - (fp)->_gptr) # define READ_DATA_PENDING_PTR(fp) ((fp)->_gptr) # endif #elif defined(_LP64) && (defined(__sun__) || defined(__sun)) typedef struct _FILE64 { unsigned char *_ptr; /* next character from/to here in buffer */ unsigned char *_base; /* the buffer */ unsigned char *_end; /* the end of the buffer */ ssize_t _cnt; /* number of available characters in buffer */ int _file; /* UNIX System file descriptor */ unsigned int _flag; /* the state of the stream */ char __fill[80]; /* filler to bring size to 128 bytes */ } FILE64; # define READ_DATA_PENDING(fp) (((FILE64*)(fp))->_cnt > 0) # define READ_DATA_PENDING_COUNT(fp) (((FILE64*)(fp))->_cnt) # define READ_DATA_PENDING_PTR(fp) ((char *)((FILE64*)(fp))->_ptr) #elif defined(FILE_COUNT) # define READ_DATA_PENDING(fp) ((fp)->FILE_COUNT > 0) # define READ_DATA_PENDING_COUNT(fp) ((fp)->FILE_COUNT) #elif defined(FILE_READEND) # define READ_DATA_PENDING(fp) ((fp)->FILE_READPTR < (fp)->FILE_READEND) # define READ_DATA_PENDING_COUNT(fp) ((fp)->FILE_READEND - (fp)->FILE_READPTR) #elif defined(__BEOS__) # define READ_DATA_PENDING(fp) (fp->_state._eof == 0) #elif defined(__VMS) # define READ_DATA_PENDING_COUNT(fp) ((unsigned int)(*(fp))->_cnt) # define READ_DATA_PENDING(fp) (((unsigned int)(*(fp))->_cnt) > 0) # define READ_DATA_BUFFERED(fp) 0 #elif defined(__DragonFly__) /* FILE is an incomplete struct type since DragonFly BSD 1.4.0 */ # define READ_DATA_PENDING(fp) (((struct __FILE_public *)(fp))->_r > 0) # define READ_DATA_PENDING_COUNT(fp) (((struct __FILE_public *)(fp))->_r) #else /* requires systems own version of the ReadDataPending() */ extern int ReadDataPending(); # define READ_DATA_PENDING(fp) (!feof(fp)) # define READ_DATA_BUFFERED(fp) 0 #endif #ifndef READ_DATA_BUFFERED # define READ_DATA_BUFFERED(fp) READ_DATA_PENDING(fp) #endif #ifndef READ_DATA_PENDING_PTR # ifdef FILE_READPTR # define READ_DATA_PENDING_PTR(fp) ((char *)(fp)->FILE_READPTR) # endif #endif #if defined __DJGPP__ # undef READ_DATA_PENDING_COUNT # undef READ_DATA_PENDING_PTR #endif #define READ_CHECK(fp) do {\ if (!READ_DATA_PENDING(fp)) {\ rb_thread_wait_fd(fileno(fp));\ rb_io_check_closed(fptr);\ }\ } while(0) void rb_eof_error() { rb_raise(rb_eEOFError, "end of file reached"); } VALUE rb_io_taint_check(io) VALUE io; { if (!OBJ_TAINTED(io) && rb_safe_level() >= 4) rb_raise(rb_eSecurityError, "Insecure: operation on untainted IO"); rb_check_frozen(io); return io; } void rb_io_check_initialized(fptr) OpenFile *fptr; { if (!fptr) { rb_raise(rb_eIOError, "uninitialized stream"); } } void rb_io_check_closed(fptr) OpenFile *fptr; { rb_io_check_initialized(fptr); if (!fptr->f && !fptr->f2) { rb_raise(rb_eIOError, "closed stream"); } } static void io_fflush _((FILE *, OpenFile *)); static OpenFile * flush_before_seek(fptr) OpenFile *fptr; { if (fptr->mode & FMODE_WBUF) { io_fflush(GetWriteFile(fptr), fptr); } errno = 0; return fptr; } #define io_seek(fptr, ofs, whence) fseeko(flush_before_seek(fptr)->f, ofs, whence) #define io_tell(fptr) ftello(flush_before_seek(fptr)->f) #ifndef SEEK_CUR # define SEEK_SET 0 # define SEEK_CUR 1 # define SEEK_END 2 #endif #define FMODE_SYNCWRITE (FMODE_SYNC|FMODE_WRITABLE) void rb_io_check_readable(fptr) OpenFile *fptr; { rb_io_check_closed(fptr); if (!(fptr->mode & FMODE_READABLE)) { rb_raise(rb_eIOError, "not opened for reading"); } #ifdef NEED_IO_SEEK_BETWEEN_RW if (((fptr->mode & FMODE_WBUF) || (fptr->mode & (FMODE_SYNCWRITE|FMODE_RBUF)) == FMODE_SYNCWRITE) && !feof(fptr->f) && !fptr->f2) { io_seek(fptr, 0, SEEK_CUR); } #endif fptr->mode |= FMODE_RBUF; } void rb_io_check_writable(fptr) OpenFile *fptr; { rb_io_check_closed(fptr); if (!(fptr->mode & FMODE_WRITABLE)) { rb_raise(rb_eIOError, "not opened for writing"); } if ((fptr->mode & FMODE_RBUF) && !feof(fptr->f) && !fptr->f2) { io_seek(fptr, 0, SEEK_CUR); } if (!fptr->f2) { fptr->mode &= ~FMODE_RBUF; } } int rb_read_pending(fp) FILE *fp; { return READ_DATA_PENDING(fp); } void rb_read_check(fp) FILE *fp; { if (!READ_DATA_PENDING(fp)) { rb_thread_wait_fd(fileno(fp)); } } static int ruby_dup(orig) int orig; { int fd; fd = dup(orig); if (fd < 0) { if (errno == EMFILE || errno == ENFILE || errno == ENOMEM) { rb_gc(); fd = dup(orig); } if (fd < 0) { rb_sys_fail(0); } } return fd; } static VALUE io_alloc _((VALUE)); static VALUE io_alloc(klass) VALUE klass; { NEWOBJ(io, struct RFile); OBJSETUP(io, klass, T_FILE); io->fptr = 0; return (VALUE)io; } static void io_fflush(f, fptr) FILE *f; OpenFile *fptr; { int n; if (!rb_thread_fd_writable(fileno(f))) { rb_io_check_closed(fptr); } for (;;) { TRAP_BEG; n = fflush(f); TRAP_END; if (n != EOF) break; if (!rb_io_wait_writable(fileno(f))) rb_sys_fail(fptr->path); } fptr->mode &= ~FMODE_WBUF; } int rb_io_wait_readable(f) int f; { fd_set rfds; switch (errno) { case EINTR: #if defined(ERESTART) case ERESTART: #endif rb_thread_wait_fd(f); return Qtrue; case EAGAIN: #if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN case EWOULDBLOCK: #endif FD_ZERO(&rfds); FD_SET(f, &rfds); rb_thread_select(f + 1, &rfds, NULL, NULL, NULL); return Qtrue; default: return Qfalse; } } int rb_io_wait_writable(f) int f; { fd_set wfds; switch (errno) { case EINTR: #if defined(ERESTART) case ERESTART: #endif rb_thread_fd_writable(f); return Qtrue; case EAGAIN: #if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN case EWOULDBLOCK: #endif FD_ZERO(&wfds); FD_SET(f, &wfds); rb_thread_select(f + 1, NULL, &wfds, NULL, NULL); return Qtrue; default: return Qfalse; } } #ifndef S_ISREG # define S_ISREG(m) ((m & S_IFMT) == S_IFREG) #endif static int wsplit_p(OpenFile *fptr) { FILE *f = GetWriteFile(fptr); int r; if (!(fptr->mode & FMODE_WSPLIT_INITIALIZED)) { struct stat buf; if (fstat(fileno(f), &buf) == 0 && !S_ISREG(buf.st_mode) #if defined(HAVE_FCNTL) && defined(F_GETFL) && defined(O_NONBLOCK) && (r = fcntl(fileno(f), F_GETFL)) != -1 && !(r & O_NONBLOCK) #endif ) { fptr->mode |= FMODE_WSPLIT; } fptr->mode |= FMODE_WSPLIT_INITIALIZED; } return fptr->mode & FMODE_WSPLIT; } /* writing functions */ static long io_fwrite(str, fptr) VALUE str; OpenFile *fptr; { long len, n, r, l, offset = 0; FILE *f = GetWriteFile(fptr); len = RSTRING(str)->len; if ((n = len) <= 0) return n; if (fptr->mode & FMODE_SYNC) { io_fflush(f, fptr); if (!rb_thread_fd_writable(fileno(f))) { rb_io_check_closed(fptr); } retry: l = n; if (PIPE_BUF < l && !rb_thread_critical && !rb_thread_alone() && wsplit_p(fptr)) { l = PIPE_BUF; } TRAP_BEG; r = write(fileno(f), RSTRING(str)->ptr+offset, l); TRAP_END; if (r == n) return len; if (0 <= r) { offset += r; n -= r; errno = EAGAIN; } if (rb_io_wait_writable(fileno(f))) { rb_io_check_closed(fptr); if (offset < RSTRING(str)->len) goto retry; } return -1L; } #if defined(__human68k__) || defined(__vms) do { if (fputc(RSTRING(str)->ptr[offset++], f) == EOF) { if (ferror(f)) return -1L; break; } } while (--n > 0); #else while (errno = 0, offset += (r = fwrite(RSTRING(str)->ptr+offset, 1, n, f)), (n -= r) > 0) { if (ferror(f) #if defined __BORLANDC__ || errno #endif ) { #ifdef __hpux if (!errno) errno = EAGAIN; #elif defined(_WIN32) && !defined(__BORLANDC__) /* workaround for MSVCRT's bug */ if (!errno) { if (GetLastError() == ERROR_NO_DATA) errno = EPIPE; else errno = EBADF; } #endif if (rb_io_wait_writable(fileno(f))) { rb_io_check_closed(fptr); clearerr(f); if (offset < RSTRING(str)->len) continue; } return -1L; } } #endif return len - n; } long rb_io_fwrite(ptr, len, f) const char *ptr; long len; FILE *f; { OpenFile of; of.f = f; of.f2 = NULL; of.mode = FMODE_WRITABLE; of.path = NULL; return io_fwrite(rb_str_new(ptr, len), &of); } /* * call-seq: * ios.write(string) => integer * * Writes the given string to ios. The stream must be opened * for writing. If the argument is not a string, it will be converted * to a string using to_s. Returns the number of bytes * written. * * count = $stdout.write( "This is a test\n" ) * puts "That was #{count} bytes of data" * * produces: * * This is a test * That was 15 bytes of data */ static VALUE io_write(io, str) VALUE io, str; { OpenFile *fptr; long n; rb_secure(4); if (TYPE(str) != T_STRING) str = rb_obj_as_string(str); if (TYPE(io) != T_FILE) { /* port is not IO, call write method for it. */ return rb_funcall(io, id_write, 1, str); } if (RSTRING(str)->len == 0) return INT2FIX(0); GetOpenFile(io, fptr); rb_io_check_writable(fptr); n = io_fwrite(str, fptr); if (n == -1L) rb_sys_fail(fptr->path); if (!(fptr->mode & FMODE_SYNC)) { fptr->mode |= FMODE_WBUF; } return LONG2FIX(n); } VALUE rb_io_write(io, str) VALUE io, str; { return rb_funcall(io, id_write, 1, str); } /* * call-seq: * ios << obj => ios * * String Output---Writes obj to ios. * obj will be converted to a string using * to_s. * * $stdout << "Hello " << "world!\n" * * produces: * * Hello world! */ VALUE rb_io_addstr(io, str) VALUE io, str; { rb_io_write(io, str); return io; } /* * call-seq: * ios.flush => ios * * Flushes any buffered data within ios to the underlying * operating system (note that this is Ruby internal buffering only; * the OS may buffer the data as well). * * $stdout.print "no newline" * $stdout.flush * * produces: * * no newline */ static VALUE rb_io_flush(io) VALUE io; { OpenFile *fptr; FILE *f; GetOpenFile(io, fptr); rb_io_check_writable(fptr); f = GetWriteFile(fptr); io_fflush(f, fptr); return io; } /* * call-seq: * ios.pos => integer * ios.tell => integer * * Returns the current offset (in bytes) of ios. * * f = File.new("testfile") * f.pos #=> 0 * f.gets #=> "This is line one\n" * f.pos #=> 17 */ static VALUE rb_io_tell(io) VALUE io; { OpenFile *fptr; off_t pos; GetOpenFile(io, fptr); pos = io_tell(fptr); if (pos < 0 && errno) rb_sys_fail(fptr->path); return OFFT2NUM(pos); } static VALUE rb_io_seek(io, offset, whence) VALUE io, offset; int whence; { OpenFile *fptr; off_t pos; pos = NUM2OFFT(offset); GetOpenFile(io, fptr); pos = io_seek(fptr, pos, whence); if (pos < 0 && errno) rb_sys_fail(fptr->path); clearerr(fptr->f); return INT2FIX(0); } /* * call-seq: * ios.seek(amount, whence=SEEK_SET) -> 0 * * Seeks to a given offset anInteger in the stream according to * the value of whence: * * IO::SEEK_CUR | Seeks to _amount_ plus current position * --------------+---------------------------------------------------- * IO::SEEK_END | Seeks to _amount_ plus end of stream (you probably * | want a negative value for _amount_) * --------------+---------------------------------------------------- * IO::SEEK_SET | Seeks to the absolute location given by _amount_ * * Example: * * f = File.new("testfile") * f.seek(-13, IO::SEEK_END) #=> 0 * f.readline #=> "And so on...\n" */ static VALUE rb_io_seek_m(argc, argv, io) int argc; VALUE *argv; VALUE io; { VALUE offset, ptrname; int whence = SEEK_SET; if (rb_scan_args(argc, argv, "11", &offset, &ptrname) == 2) { whence = NUM2INT(ptrname); } return rb_io_seek(io, offset, whence); } /* * call-seq: * ios.pos = integer => integer * * Seeks to the given position (in bytes) in ios. * * f = File.new("testfile") * f.pos = 17 * f.gets #=> "This is line two\n" */ static VALUE rb_io_set_pos(io, offset) VALUE io, offset; { OpenFile *fptr; off_t pos; pos = NUM2OFFT(offset); GetOpenFile(io, fptr); pos = io_seek(fptr, pos, SEEK_SET); if (pos != 0) rb_sys_fail(fptr->path); clearerr(fptr->f); return OFFT2NUM(pos); } /* * call-seq: * ios.rewind => 0 * * Positions ios to the beginning of input, resetting * lineno to zero. * * f = File.new("testfile") * f.readline #=> "This is line one\n" * f.rewind #=> 0 * f.lineno #=> 0 * f.readline #=> "This is line one\n" */ static VALUE rb_io_rewind(io) VALUE io; { OpenFile *fptr; GetOpenFile(io, fptr); if (io_seek(fptr, 0L, 0) != 0) rb_sys_fail(fptr->path); clearerr(fptr->f); if (io == current_file) { gets_lineno -= fptr->lineno; } fptr->lineno = 0; return INT2FIX(0); } /* * call-seq: * ios.eof => true or false * ios.eof? => true or false * * Returns true if ios is at end of file that means * there are no more data to read. * The stream must be opened for reading or an IOError will be * raised. * * f = File.new("testfile") * dummy = f.readlines * f.eof #=> true * * If ios is a stream such as pipe or socket, IO#eof? * blocks until the other end sends some data or closes it. * * r, w = IO.pipe * Thread.new { sleep 1; w.close } * r.eof? #=> true after 1 second blocking * * r, w = IO.pipe * Thread.new { sleep 1; w.puts "a" } * r.eof? #=> false after 1 second blocking * * r, w = IO.pipe * r.eof? # blocks forever * * Note that IO#eof? reads data to a input buffer. * So IO#sysread doesn't work with IO#eof?. */ VALUE rb_io_eof(io) VALUE io; { OpenFile *fptr; int ch; GetOpenFile(io, fptr); rb_io_check_readable(fptr); if (feof(fptr->f)) return Qtrue; if (READ_DATA_PENDING(fptr->f)) return Qfalse; READ_CHECK(fptr->f); clearerr(fptr->f); TRAP_BEG; ch = getc(fptr->f); TRAP_END; if (ch != EOF) { ungetc(ch, fptr->f); return Qfalse; } rb_io_check_closed(fptr); clearerr(fptr->f); return Qtrue; } /* * call-seq: * ios.sync => true or false * * Returns the current ``sync mode'' of ios. When sync mode is * true, all output is immediately flushed to the underlying operating * system and is not buffered by Ruby internally. See also * IO#fsync. * * f = File.new("testfile") * f.sync #=> false */ static VALUE rb_io_sync(io) VALUE io; { OpenFile *fptr; GetOpenFile(io, fptr); return (fptr->mode & FMODE_SYNC) ? Qtrue : Qfalse; } /* * call-seq: * ios.sync = boolean => boolean * * Sets the ``sync mode'' to true or false. * When sync mode is true, all output is immediately flushed to the * underlying operating system and is not buffered internally. Returns * the new state. See also IO#fsync. * * f = File.new("testfile") * f.sync = true * * (produces no output) */ static VALUE rb_io_set_sync(io, mode) VALUE io, mode; { OpenFile *fptr; GetOpenFile(io, fptr); if (RTEST(mode)) { fptr->mode |= FMODE_SYNC; } else { fptr->mode &= ~FMODE_SYNC; } return mode; } /* * call-seq: * ios.fsync => 0 or nil * * Immediately writes all buffered data in ios to disk. * Returns nil if the underlying operating system does not * support fsync(2). Note that fsync differs from * using IO#sync=. The latter ensures that data is flushed * from Ruby's buffers, but doesn't not guarantee that the underlying * operating system actually writes it to disk. */ static VALUE rb_io_fsync(io) VALUE io; { #ifdef HAVE_FSYNC OpenFile *fptr; FILE *f; GetOpenFile(io, fptr); f = GetWriteFile(fptr); io_fflush(f, fptr); if (fsync(fileno(f)) < 0) rb_sys_fail(fptr->path); return INT2FIX(0); #else rb_notimplement(); return Qnil; /* not reached */ #endif } /* * call-seq: * ios.fileno => fixnum * ios.to_i => fixnum * * Returns an integer representing the numeric file descriptor for * ios. * * $stdin.fileno #=> 0 * $stdout.fileno #=> 1 */ static VALUE rb_io_fileno(io) VALUE io; { OpenFile *fptr; int fd; GetOpenFile(io, fptr); fd = fileno(fptr->f); return INT2FIX(fd); } /* * call-seq: * ios.pid => fixnum * * Returns the process ID of a child process associated with * ios. This will be set by IO::popen. * * pipe = IO.popen("-") * if pipe * $stderr.puts "In parent, child pid is #{pipe.pid}" * else * $stderr.puts "In child, pid is #{$$}" * end * * produces: * * In child, pid is 26209 * In parent, child pid is 26209 */ static VALUE rb_io_pid(io) VALUE io; { OpenFile *fptr; GetOpenFile(io, fptr); if (!fptr->pid) return Qnil; return INT2FIX(fptr->pid); } /* * call-seq: * ios.inspect => string * * Return a string describing this IO object. */ static VALUE rb_io_inspect(obj) VALUE obj; { OpenFile *fptr; char *buf, *cname, *st = ""; long len; fptr = RFILE(rb_io_taint_check(obj))->fptr; if (!fptr || !fptr->path) return rb_any_to_s(obj); cname = rb_obj_classname(obj); len = strlen(cname) + strlen(fptr->path) + 5; if (!(fptr->f || fptr->f2)) { st = " (closed)"; len += 9; } buf = ALLOCA_N(char, len); snprintf(buf, len, "#<%s:%s%s>", cname, fptr->path, st); return rb_str_new2(buf); } /* * call-seq: * ios.to_io -> ios * * Returns ios. */ static VALUE rb_io_to_io(io) VALUE io; { return io; } /* reading functions */ static long read_buffered_data(ptr, len, f) char *ptr; long len; FILE *f; { long n; #ifdef READ_DATA_PENDING_COUNT n = READ_DATA_PENDING_COUNT(f); if (n <= 0) return 0; if (n > len) n = len; return fread(ptr, 1, n, f); #else int c; for (n = 0; n < len && READ_DATA_PENDING(f) && (c = getc(f)) != EOF; ++n) { *ptr++ = c; } return n; #endif } static long io_fread(ptr, len, fptr) char *ptr; long len; OpenFile *fptr; { long n = len; int c; int saved_errno; while (n > 0) { c = read_buffered_data(ptr, n, fptr->f); if (c < 0) goto eof; if (c > 0) { ptr += c; if ((n -= c) <= 0) break; } rb_thread_wait_fd(fileno(fptr->f)); rb_io_check_closed(fptr); clearerr(fptr->f); TRAP_BEG; c = getc(fptr->f); TRAP_END; if (c == EOF) { eof: if (ferror(fptr->f)) { switch (errno) { case EINTR: #if defined(ERESTART) case ERESTART: #endif clearerr(fptr->f); continue; case EAGAIN: #if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN case EWOULDBLOCK: #endif if (len > n) { clearerr(fptr->f); } saved_errno = errno; rb_warning("nonblocking IO#read is obsolete; use IO#readpartial or IO#sysread"); errno = saved_errno; } if (len == n) return 0; } break; } *ptr++ = c; n--; } return len - n; } long rb_io_fread(ptr, len, f) char *ptr; long len; FILE *f; { OpenFile of; of.f = f; of.f2 = NULL; return io_fread(ptr, len, &of); } #define SMALLBUF 100 static long remain_size(fptr) OpenFile *fptr; { struct stat st; off_t siz = BUFSIZ; off_t pos; if (feof(fptr->f)) return 0; if (fstat(fileno(fptr->f), &st) == 0 && S_ISREG(st.st_mode) #ifdef __BEOS__ && (st.st_dev > 3) #endif ) { pos = io_tell(fptr); if (st.st_size >= pos && pos >= 0) { siz = st.st_size - pos + 1; if (siz > LONG_MAX) { rb_raise(rb_eIOError, "file too big for single read"); } } } return (long)siz; } static VALUE read_all(fptr, siz, str) OpenFile *fptr; long siz; VALUE str; { long bytes = 0; long n; if (siz == 0) siz = BUFSIZ; if (NIL_P(str)) { str = rb_str_new(0, siz); } else { rb_str_resize(str, siz); } for (;;) { rb_str_locktmp(str); READ_CHECK(fptr->f); n = io_fread(RSTRING(str)->ptr+bytes, siz-bytes, fptr); rb_str_unlocktmp(str); if (n == 0 && bytes == 0) { if (!fptr->f) break; if (feof(fptr->f)) break; if (!ferror(fptr->f)) break; rb_sys_fail(fptr->path); } bytes += n; if (bytes < siz) break; siz += BUFSIZ; rb_str_resize(str, siz); } if (bytes != siz) rb_str_resize(str, bytes); OBJ_TAINT(str); return str; } void rb_io_set_nonblock(OpenFile *fptr) { int flags; #ifdef F_GETFL flags = fcntl(fileno(fptr->f), F_GETFL); if (flags == -1) { rb_sys_fail(fptr->path); } #else flags = 0; #endif if ((flags & O_NONBLOCK) == 0) { flags |= O_NONBLOCK; if (fcntl(fileno(fptr->f), F_SETFL, flags) == -1) { rb_sys_fail(fptr->path); } } if (fptr->f2) { #ifdef F_GETFL flags = fcntl(fileno(fptr->f2), F_GETFL); if (flags == -1) { rb_sys_fail(fptr->path); } #else flags = 0; #endif if ((flags & O_NONBLOCK) == 0) { flags |= O_NONBLOCK; if (fcntl(fileno(fptr->f2), F_SETFL, flags) == -1) { rb_sys_fail(fptr->path); } } } } static VALUE io_getpartial(int argc, VALUE *argv, VALUE io, int nonblock) { OpenFile *fptr; VALUE length, str; long n, len; rb_scan_args(argc, argv, "11", &length, &str); if ((len = NUM2LONG(length)) < 0) { rb_raise(rb_eArgError, "negative length %ld given", len); } if (NIL_P(str)) { str = rb_str_new(0, len); } else { StringValue(str); rb_str_modify(str); rb_str_resize(str, len); } OBJ_TAINT(str); GetOpenFile(io, fptr); rb_io_check_readable(fptr); if (len == 0) return str; if (!nonblock) { READ_CHECK(fptr->f); } if (RSTRING(str)->len != len) { modified: rb_raise(rb_eRuntimeError, "buffer string modified"); } n = read_buffered_data(RSTRING(str)->ptr, len, fptr->f); if (n <= 0) { again: if (RSTRING(str)->len != len) goto modified; if (nonblock) { rb_io_set_nonblock(fptr); n = read(fileno(fptr->f), RSTRING(str)->ptr, len); } else { TRAP_BEG; n = read(fileno(fptr->f), RSTRING(str)->ptr, len); TRAP_END; } if (n < 0) { if (!nonblock && rb_io_wait_readable(fileno(fptr->f))) goto again; rb_sys_fail(fptr->path); } if (fptr->f) /* update pos in FILE structure [ruby-core:21561] */ fflush(fptr->f); } rb_str_resize(str, n); if (n == 0) return Qnil; else return str; } /* * call-seq: * ios.readpartial(maxlen) => string * ios.readpartial(maxlen, outbuf) => outbuf * * Reads at most maxlen bytes from the I/O stream. * It blocks only if ios has no data immediately available. * It doesn't block if some data available. * If the optional outbuf argument is present, * it must reference a String, which will receive the data. * It raises EOFError on end of file. * * readpartial is designed for streams such as pipe, socket, tty, etc. * It blocks only when no data immediately available. * This means that it blocks only when following all conditions hold. * * the buffer in the IO object is empty. * * the content of the stream is empty. * * the stream is not reached to EOF. * * When readpartial blocks, it waits data or EOF on the stream. * If some data is reached, readpartial returns with the data. * If EOF is reached, readpartial raises EOFError. * * When readpartial doesn't blocks, it returns or raises immediately. * If the buffer is not empty, it returns the data in the buffer. * Otherwise if the stream has some content, * it returns the data in the stream. * Otherwise if the stream is reached to EOF, it raises EOFError. * * r, w = IO.pipe # buffer pipe content * w << "abc" # "" "abc". * r.readpartial(4096) #=> "abc" "" "" * r.readpartial(4096) # blocks because buffer and pipe is empty. * * r, w = IO.pipe # buffer pipe content * w << "abc" # "" "abc" * w.close # "" "abc" EOF * r.readpartial(4096) #=> "abc" "" EOF * r.readpartial(4096) # raises EOFError * * r, w = IO.pipe # buffer pipe content * w << "abc\ndef\n" # "" "abc\ndef\n" * r.gets #=> "abc\n" "def\n" "" * w << "ghi\n" # "def\n" "ghi\n" * r.readpartial(4096) #=> "def\n" "" "ghi\n" * r.readpartial(4096) #=> "ghi\n" "" "" * * Note that readpartial behaves similar to sysread. * The differences are: * * If the buffer is not empty, read from the buffer instead of "sysread for buffered IO (IOError)". * * It doesn't cause Errno::EAGAIN and Errno::EINTR. When readpartial meets EAGAIN and EINTR by read system call, readpartial retry the system call. * * The later means that readpartial is nonblocking-flag insensitive. * It blocks on the situation IO#sysread causes Errno::EAGAIN as if the fd is blocking mode. * */ static VALUE io_readpartial(int argc, VALUE *argv, VALUE io) { VALUE ret; ret = io_getpartial(argc, argv, io, 0); if (NIL_P(ret)) rb_eof_error(); else return ret; } /* * call-seq: * ios.read_nonblock(maxlen) => string * ios.read_nonblock(maxlen, outbuf) => outbuf * * Reads at most maxlen bytes from ios using * read(2) system call after O_NONBLOCK is set for * the underlying file descriptor. * * If the optional outbuf argument is present, * it must reference a String, which will receive the data. * * read_nonblock just calls read(2). * It causes all errors read(2) causes: EAGAIN, EINTR, etc. * The caller should care such errors. * * read_nonblock causes EOFError on EOF. * * If the read buffer is not empty, * read_nonblock reads from the buffer like readpartial. * In this case, read(2) is not called. * */ static VALUE io_read_nonblock(int argc, VALUE *argv, VALUE io) { VALUE ret; ret = io_getpartial(argc, argv, io, 1); if (NIL_P(ret)) rb_eof_error(); else return ret; } /* * call-seq: * ios.write_nonblock(string) => integer * * Writes the given string to ios using * write(2) system call after O_NONBLOCK is set for * the underlying file descriptor. * * write_nonblock just calls write(2). * It causes all errors write(2) causes: EAGAIN, EINTR, etc. * The result may also be smaller than string.length (partial write). * The caller should care such errors and partial write. * */ static VALUE rb_io_write_nonblock(VALUE io, VALUE str) { OpenFile *fptr; FILE *f; long n; rb_secure(4); if (TYPE(str) != T_STRING) str = rb_obj_as_string(str); GetOpenFile(io, fptr); rb_io_check_writable(fptr); f = GetWriteFile(fptr); rb_io_set_nonblock(fptr); n = write(fileno(f), RSTRING(str)->ptr, RSTRING(str)->len); if (n == -1) rb_sys_fail(fptr->path); return LONG2FIX(n); } /* * call-seq: * ios.read([length [, buffer]]) => string, buffer, or nil * * Reads at most length bytes from the I/O stream, or to the * end of file if length is omitted or is nil. * length must be a non-negative integer or nil. * If the optional buffer argument is present, it must reference * a String, which will receive the data. * * At end of file, it returns nil or "" * depend on length. * ios.read() and * ios.read(nil) returns "". * ios.read(positive-integer) returns nil. * * f = File.new("testfile") * f.read(16) #=> "This is line one" */ static VALUE io_read(argc, argv, io) int argc; VALUE *argv; VALUE io; { OpenFile *fptr; long n, len; VALUE length, str; rb_scan_args(argc, argv, "02", &length, &str); if (NIL_P(length)) { if (!NIL_P(str)) StringValue(str); GetOpenFile(io, fptr); rb_io_check_readable(fptr); return read_all(fptr, remain_size(fptr), str); } len = NUM2LONG(length); if (len < 0) { rb_raise(rb_eArgError, "negative length %ld given", len); } if (NIL_P(str)) { str = rb_tainted_str_new(0, len); } else { StringValue(str); rb_str_modify(str); rb_str_resize(str,len); } GetOpenFile(io, fptr); rb_io_check_readable(fptr); if (feof(fptr->f)) return Qnil; if (len == 0) return str; rb_str_locktmp(str); READ_CHECK(fptr->f); if (RSTRING(str)->len != len) { rb_raise(rb_eRuntimeError, "buffer string modified"); } n = io_fread(RSTRING(str)->ptr, len, fptr); rb_str_unlocktmp(str); if (n == 0) { if (!fptr->f) return Qnil; if (feof(fptr->f)) { rb_str_resize(str, 0); return Qnil; } if (len > 0) rb_sys_fail(fptr->path); } rb_str_resize(str, n); RSTRING(str)->len = n; RSTRING(str)->ptr[n] = '\0'; OBJ_TAINT(str); return str; } static int appendline(fptr, delim, strp) OpenFile *fptr; int delim; VALUE *strp; { FILE *f = fptr->f; VALUE str = *strp; int c = EOF; #ifndef READ_DATA_PENDING_PTR char buf[8192]; char *bp = buf, *bpe = buf + sizeof buf - 3; int update = Qfalse; #endif do { #ifdef READ_DATA_PENDING_PTR long pending = READ_DATA_PENDING_COUNT(f); if (pending > 0) { const char *p = READ_DATA_PENDING_PTR(f); const char *e = memchr(p, delim, pending); long last = 0, len = (c != EOF); if (e) pending = e - p + 1; len += pending; if (!NIL_P(str)) { last = RSTRING(str)->len; rb_str_resize(str, last + len); } else { *strp = str = rb_str_buf_new(len); RSTRING(str)->len = len; RSTRING(str)->ptr[len] = '\0'; } if (c != EOF) { RSTRING(str)->ptr[last++] = c; } fread(RSTRING(str)->ptr + last, 1, pending, f); /* must not fail */ if (e) return delim; } else if (c != EOF) { if (!NIL_P(str)) { char ch = c; rb_str_buf_cat(str, &ch, 1); } else { *strp = str = rb_str_buf_new(1); RSTRING(str)->ptr[RSTRING(str)->len++] = c; } } rb_thread_wait_fd(fileno(f)); rb_io_check_closed(fptr); #else READ_CHECK(f); #endif clearerr(f); TRAP_BEG; c = getc(f); TRAP_END; if (c == EOF) { if (ferror(f)) { clearerr(f); if (!rb_io_wait_readable(fileno(f))) rb_sys_fail(fptr->path); continue; } #ifdef READ_DATA_PENDING_PTR return c; #endif } #ifndef READ_DATA_PENDING_PTR if (c == EOF || (*bp++ = c) == delim || bp == bpe) { int cnt = bp - buf; if (cnt > 0) { if (!NIL_P(str)) rb_str_cat(str, buf, cnt); else *strp = str = rb_str_new(buf, cnt); } if (c == EOF) { if (update) return (int)RSTRING(str)->ptr[RSTRING(str)->len-1]; return c; } bp = buf; } update = Qtrue; #endif } while (c != delim); #ifdef READ_DATA_PENDING_PTR { char ch = c; if (!NIL_P(str)) { rb_str_cat(str, &ch, 1); } else { *strp = str = rb_str_new(&ch, 1); } } #endif return c; } static inline int swallow(fptr, term) OpenFile *fptr; int term; { FILE *f = fptr->f; int c; do { #ifdef READ_DATA_PENDING_PTR long cnt; while ((cnt = READ_DATA_PENDING_COUNT(f)) > 0) { char buf[1024]; const char *p = READ_DATA_PENDING_PTR(f); int i; if (cnt > sizeof buf) cnt = sizeof buf; if (*p != term) return Qtrue; i = cnt; while (--i && *++p == term); if (!fread(buf, 1, cnt - i, f)) /* must not fail */ rb_sys_fail(fptr->path); } rb_thread_wait_fd(fileno(f)); rb_io_check_closed(fptr); #else READ_CHECK(f); #endif clearerr(f); TRAP_BEG; c = getc(f); TRAP_END; if (c != term) { ungetc(c, f); return Qtrue; } } while (c != EOF); return Qfalse; } static VALUE rb_io_getline_fast(fptr, delim) OpenFile *fptr; unsigned char delim; { VALUE str = Qnil; int c; while ((c = appendline(fptr, delim, &str)) != EOF && c != delim); if (!NIL_P(str)) { fptr->lineno++; lineno = INT2FIX(fptr->lineno); OBJ_TAINT(str); } return str; } static int rscheck(rsptr, rslen, rs) char *rsptr; long rslen; VALUE rs; { if (RSTRING(rs)->ptr != rsptr && RSTRING(rs)->len != rslen) rb_raise(rb_eRuntimeError, "rs modified"); return 1; } static VALUE rb_io_getline(VALUE rs, VALUE io); static VALUE rb_io_getline(rs, io) VALUE rs, io; { VALUE str = Qnil; OpenFile *fptr; GetOpenFile(io, fptr); rb_io_check_readable(fptr); if (NIL_P(rs)) { str = read_all(fptr, 0, Qnil); if (RSTRING(str)->len == 0) return Qnil; } else if (rs == rb_default_rs) { return rb_io_getline_fast(fptr, '\n'); } else { int c, newline; char *rsptr; long rslen; int rspara = 0; rslen = RSTRING(rs)->len; if (rslen == 0) { rsptr = "\n\n"; rslen = 2; rspara = 1; swallow(fptr, '\n'); } else if (rslen == 1) { return rb_io_getline_fast(fptr, (unsigned char)RSTRING(rs)->ptr[0]); } else { rsptr = RSTRING(rs)->ptr; } newline = rsptr[rslen - 1]; while ((c = appendline(fptr, newline, &str)) != EOF && (c != newline || RSTRING(str)->len < rslen || ((rspara || rscheck(rsptr,rslen,rs)) && 0) || memcmp(RSTRING(str)->ptr+RSTRING(str)->len-rslen,rsptr,rslen))); if (rspara) { if (c != EOF) { swallow(fptr, '\n'); } } } if (!NIL_P(str)) { fptr->lineno++; lineno = INT2FIX(fptr->lineno); OBJ_TAINT(str); } return str; } VALUE rb_io_gets(io) VALUE io; { OpenFile *fptr; GetOpenFile(io, fptr); rb_io_check_readable(fptr); return rb_io_getline_fast(fptr, '\n'); } /* * call-seq: * ios.gets(sep_string=$/) => string or nil * * Reads the next ``line'' from the I/O stream; lines are separated by * sep_string. A separator of nil reads the entire * contents, and a zero-length separator reads the input a paragraph at * a time (two successive newlines in the input separate paragraphs). * The stream must be opened for reading or an IOError * will be raised. The line read in will be returned and also assigned * to $_. Returns nil if called at end of * file. * * File.new("testfile").gets #=> "This is line one\n" * $_ #=> "This is line one\n" */ static VALUE rb_io_gets_m(argc, argv, io) int argc; VALUE *argv; VALUE io; { VALUE rs, str; if (argc == 0) { rs = rb_rs; } else { rb_scan_args(argc, argv, "1", &rs); if (!NIL_P(rs)) StringValue(rs); } str = rb_io_getline(rs, io); rb_lastline_set(str); return str; } /* * call-seq: * ios.lineno => integer * * Returns the current line number in ios. The stream must be * opened for reading. lineno counts the number of times * gets is called, rather than the number of newlines * encountered. The two values will differ if gets is * called with a separator other than newline. See also the * $. variable. * * f = File.new("testfile") * f.lineno #=> 0 * f.gets #=> "This is line one\n" * f.lineno #=> 1 * f.gets #=> "This is line two\n" * f.lineno #=> 2 */ static VALUE rb_io_lineno(io) VALUE io; { OpenFile *fptr; GetOpenFile(io, fptr); rb_io_check_readable(fptr); return INT2NUM(fptr->lineno); } /* * call-seq: * ios.lineno = integer => integer * * Manually sets the current line number to the given value. * $. is updated only on the next read. * * f = File.new("testfile") * f.gets #=> "This is line one\n" * $. #=> 1 * f.lineno = 1000 * f.lineno #=> 1000 * $. # lineno of last read #=> 1 * f.gets #=> "This is line two\n" * $. # lineno of last read #=> 1001 */ static VALUE rb_io_set_lineno(io, lineno) VALUE io, lineno; { OpenFile *fptr; GetOpenFile(io, fptr); rb_io_check_readable(fptr); fptr->lineno = NUM2INT(lineno); return lineno; } static void lineno_setter(val, id, var) VALUE val; ID id; VALUE *var; { gets_lineno = NUM2INT(val); *var = INT2FIX(gets_lineno); } static VALUE argf_set_lineno(argf, val) VALUE argf, val; { gets_lineno = NUM2INT(val); lineno = INT2FIX(gets_lineno); return Qnil; } static VALUE argf_lineno() { return lineno; } /* * call-seq: * ios.readline(sep_string=$/) => string * * Reads a line as with IO#gets, but raises an * EOFError on end of file. */ static VALUE rb_io_readline(argc, argv, io) int argc; VALUE *argv; VALUE io; { VALUE line = rb_io_gets_m(argc, argv, io); if (NIL_P(line)) { rb_eof_error(); } return line; } /* * call-seq: * ios.readlines(sep_string=$/) => array * * Reads all of the lines in ios, and returns them in * anArray. Lines are separated by the optional * sep_string. If sep_string is nil, the * rest of the stream is returned as a single record. * The stream must be opened for reading or an * IOError will be raised. * * f = File.new("testfile") * f.readlines[0] #=> "This is line one\n" */ static VALUE rb_io_readlines(argc, argv, io) int argc; VALUE *argv; VALUE io; { VALUE line, ary; VALUE rs; if (argc == 0) { rs = rb_rs; } else { rb_scan_args(argc, argv, "1", &rs); if (!NIL_P(rs)) StringValue(rs); } ary = rb_ary_new(); while (!NIL_P(line = rb_io_getline(rs, io))) { rb_ary_push(ary, line); } return ary; } /* * call-seq: * ios.each(sep_string=$/) {|line| block } => ios * ios.each_line(sep_string=$/) {|line| block } => ios * * Executes the block for every line in ios, where lines are * separated by sep_string. ios must be opened for * reading or an IOError will be raised. * * f = File.new("testfile") * f.each {|line| puts "#{f.lineno}: #{line}" } * * produces: * * 1: This is line one * 2: This is line two * 3: This is line three * 4: And so on... */ static VALUE rb_io_each_line(argc, argv, io) int argc; VALUE *argv; VALUE io; { VALUE str; VALUE rs; if (argc == 0) { rs = rb_rs; } else { rb_scan_args(argc, argv, "1", &rs); if (!NIL_P(rs)) StringValue(rs); } while (!NIL_P(str = rb_io_getline(rs, io))) { rb_yield(str); } return io; } /* * call-seq: * ios.each_byte {|byte| block } => nil * * Calls the given block once for each byte (0..255) in ios, * passing the byte as an argument. The stream must be opened for * reading or an IOError will be raised. * * f = File.new("testfile") * checksum = 0 * f.each_byte {|x| checksum ^= x } #=> # * checksum #=> 12 */ static VALUE rb_io_each_byte(io) VALUE io; { OpenFile *fptr; FILE *f; int c; GetOpenFile(io, fptr); for (;;) { rb_io_check_readable(fptr); f = fptr->f; READ_CHECK(f); clearerr(f); TRAP_BEG; c = getc(f); TRAP_END; if (c == EOF) { if (ferror(f)) { clearerr(f); if (!rb_io_wait_readable(fileno(f))) rb_sys_fail(fptr->path); continue; } break; } rb_yield(INT2FIX(c & 0xff)); } if (ferror(f)) rb_sys_fail(fptr->path); return io; } /* * call-seq: * ios.getc => fixnum or nil * * Gets the next 8-bit byte (0..255) from ios. Returns * nil if called at end of file. * * f = File.new("testfile") * f.getc #=> 84 * f.getc #=> 104 */ VALUE rb_io_getc(io) VALUE io; { OpenFile *fptr; FILE *f; int c; GetOpenFile(io, fptr); rb_io_check_readable(fptr); f = fptr->f; retry: READ_CHECK(f); clearerr(f); TRAP_BEG; c = getc(f); TRAP_END; if (c == EOF) { if (ferror(f)) { clearerr(f); if (!rb_io_wait_readable(fileno(f))) rb_sys_fail(fptr->path); goto retry; } return Qnil; } return INT2FIX(c & 0xff); } int rb_getc(f) FILE *f; { int c; if (!READ_DATA_PENDING(f)) { rb_thread_wait_fd(fileno(f)); } clearerr(f); TRAP_BEG; c = getc(f); TRAP_END; return c; } /* * call-seq: * ios.readchar => fixnum * * Reads a character as with IO#getc, but raises an * EOFError on end of file. */ static VALUE rb_io_readchar(io) VALUE io; { VALUE c = rb_io_getc(io); if (NIL_P(c)) { rb_eof_error(); } return c; } /* * call-seq: * ios.ungetc(integer) => nil * * Pushes back one character (passed as a parameter) onto ios, * such that a subsequent buffered read will return it. Only one character * may be pushed back before a subsequent read operation (that is, * you will be able to read only the last of several characters that have been pushed * back). Has no effect with unbuffered reads (such as IO#sysread). * * f = File.new("testfile") #=> # * c = f.getc #=> 84 * f.ungetc(c) #=> nil * f.getc #=> 84 */ VALUE rb_io_ungetc(io, c) VALUE io, c; { OpenFile *fptr; int cc = NUM2INT(c); GetOpenFile(io, fptr); if (!(fptr->mode & FMODE_RBUF)) rb_raise(rb_eIOError, "unread stream"); rb_io_check_readable(fptr); if (ungetc(cc, fptr->f) == EOF && cc != EOF) { rb_raise(rb_eIOError, "ungetc failed"); } return Qnil; } /* * call-seq: * ios.isatty => true or false * ios.tty? => true or false * * Returns true if ios is associated with a * terminal device (tty), false otherwise. * * File.new("testfile").isatty #=> false * File.new("/dev/tty").isatty #=> true */ static VALUE rb_io_isatty(io) VALUE io; { OpenFile *fptr; GetOpenFile(io, fptr); if (isatty(fileno(fptr->f)) == 0) return Qfalse; return Qtrue; } static void fptr_finalize(fptr, noraise) OpenFile *fptr; int noraise; { int n1 = 0, n2 = 0, f1, f2 = -1; errno = 0; if (fptr->f2) { f2 = fileno(fptr->f2); while (n2 = 0, fflush(fptr->f2) < 0) { n2 = errno; if (!rb_io_wait_writable(f2)) { break; } if (!fptr->f2) break; } if (fclose(fptr->f2) < 0 && n2 == 0) { n2 = errno; } fptr->f2 = 0; } if (fptr->f) { f1 = fileno(fptr->f); if ((f2 == -1) && (fptr->mode & FMODE_WBUF)) { while (n1 = 0, fflush(fptr->f) < 0) { n1 = errno; if (!rb_io_wait_writable(f1)) break; if (!fptr->f) break; } } if (fclose(fptr->f) < 0 && n1 == 0) { n1 = errno; } fptr->f = 0; if (n1 == EBADF && f1 == f2) { n1 = 0; } } if (!noraise && (n1 || n2)) { errno = (n1 ? n1 : n2); rb_sys_fail(fptr->path); } } static void rb_io_fptr_cleanup(fptr, noraise) OpenFile *fptr; int noraise; { if (fptr->finalize) { (*fptr->finalize)(fptr, noraise); } else { fptr_finalize(fptr, noraise); } } void rb_io_fptr_finalize(fptr) OpenFile *fptr; { if (!fptr) return; if (fptr->path) { free(fptr->path); } if (!fptr->f && !fptr->f2) return; if (fileno(fptr->f) < 3) return; rb_io_fptr_cleanup(fptr, Qtrue); } VALUE rb_io_close(io) VALUE io; { OpenFile *fptr; int fd, fd2; fptr = RFILE(io)->fptr; if (!fptr) return Qnil; if (fptr->f2) { fd2 = fileno(fptr->f2); } else { if (!fptr->f) return Qnil; fd2 = -1; } fd = fileno(fptr->f); rb_io_fptr_cleanup(fptr, Qfalse); rb_thread_fd_close(fd); if (fd2 >= 0) rb_thread_fd_close(fd2); if (fptr->pid) { rb_syswait(fptr->pid); fptr->pid = 0; } return Qnil; } /* * call-seq: * ios.close => nil * * Closes ios and flushes any pending writes to the operating * system. The stream is unavailable for any further data operations; * an IOError is raised if such an attempt is made. I/O * streams are automatically closed when they are claimed by the * garbage collector. * * If ios is opened by IO.popen, * close sets $?. */ static VALUE rb_io_close_m(io) VALUE io; { if (rb_safe_level() >= 4 && !OBJ_TAINTED(io)) { rb_raise(rb_eSecurityError, "Insecure: can't close"); } rb_io_check_closed(RFILE(io)->fptr); rb_io_close(io); return Qnil; } static VALUE io_call_close(io) VALUE io; { return rb_funcall(io, rb_intern("close"), 0, 0); } static VALUE io_close(io) VALUE io; { return rb_rescue(io_call_close, io, 0, 0); } /* * call-seq: * ios.closed? => true or false * * Returns true if ios is completely closed (for * duplex streams, both reader and writer), false * otherwise. * * f = File.new("testfile") * f.close #=> nil * f.closed? #=> true * f = IO.popen("/bin/sh","r+") * f.close_write #=> nil * f.closed? #=> false * f.close_read #=> nil * f.closed? #=> true */ static VALUE rb_io_closed(io) VALUE io; { OpenFile *fptr; fptr = RFILE(io)->fptr; rb_io_check_initialized(fptr); return (fptr->f || fptr->f2)?Qfalse:Qtrue; } /* * call-seq: * ios.close_read => nil * * Closes the read end of a duplex I/O stream (i.e., one that contains * both a read and a write stream, such as a pipe). Will raise an * IOError if the stream is not duplexed. * * f = IO.popen("/bin/sh","r+") * f.close_read * f.readlines * * produces: * * prog.rb:3:in `readlines': not opened for reading (IOError) * from prog.rb:3 */ static VALUE rb_io_close_read(io) VALUE io; { OpenFile *fptr; int n; if (rb_safe_level() >= 4 && !OBJ_TAINTED(io)) { rb_raise(rb_eSecurityError, "Insecure: can't close"); } GetOpenFile(io, fptr); if (fptr->f2 == 0 && (fptr->mode & FMODE_WRITABLE)) { rb_raise(rb_eIOError, "closing non-duplex IO for reading"); } if (fptr->f2 == 0) { return rb_io_close(io); } n = fclose(fptr->f); fptr->mode &= ~FMODE_READABLE; fptr->f = fptr->f2; fptr->f2 = 0; if (n != 0) rb_sys_fail(fptr->path); return Qnil; } /* * call-seq: * ios.close_write => nil * * Closes the write end of a duplex I/O stream (i.e., one that contains * both a read and a write stream, such as a pipe). Will raise an * IOError if the stream is not duplexed. * * f = IO.popen("/bin/sh","r+") * f.close_write * f.print "nowhere" * * produces: * * prog.rb:3:in `write': not opened for writing (IOError) * from prog.rb:3:in `print' * from prog.rb:3 */ static VALUE rb_io_close_write(io) VALUE io; { OpenFile *fptr; int n; if (rb_safe_level() >= 4 && !OBJ_TAINTED(io)) { rb_raise(rb_eSecurityError, "Insecure: can't close"); } GetOpenFile(io, fptr); if (fptr->f2 == 0 && (fptr->mode & FMODE_READABLE)) { rb_raise(rb_eIOError, "closing non-duplex IO for writing"); } if (fptr->f2 == 0) { return rb_io_close(io); } n = fclose(fptr->f2); fptr->f2 = 0; fptr->mode &= ~FMODE_WRITABLE; if (n != 0) rb_sys_fail(fptr->path); return Qnil; } /* * call-seq: * ios.sysseek(offset, whence=SEEK_SET) => integer * * Seeks to a given offset in the stream according to the value * of whence (see IO#seek for values of * whence). Returns the new offset into the file. * * f = File.new("testfile") * f.sysseek(-13, IO::SEEK_END) #=> 53 * f.sysread(10) #=> "And so on." */ static VALUE rb_io_sysseek(argc, argv, io) int argc; VALUE *argv; VALUE io; { VALUE offset, ptrname; int whence = SEEK_SET; OpenFile *fptr; off_t pos; if (rb_scan_args(argc, argv, "11", &offset, &ptrname) == 2) { whence = NUM2INT(ptrname); } pos = NUM2OFFT(offset); GetOpenFile(io, fptr); if ((fptr->mode & FMODE_READABLE) && READ_DATA_BUFFERED(fptr->f)) { rb_raise(rb_eIOError, "sysseek for buffered IO"); } if ((fptr->mode & FMODE_WRITABLE) && (fptr->mode & FMODE_WBUF)) { rb_warn("sysseek for buffered IO"); } pos = lseek(fileno(fptr->f), pos, whence); if (pos == -1) rb_sys_fail(fptr->path); clearerr(fptr->f); return OFFT2NUM(pos); } /* * call-seq: * ios.syswrite(string) => integer * * Writes the given string to ios using a low-level write. * Returns the number of bytes written. Do not mix with other methods * that write to ios or you may get unpredictable results. * Raises SystemCallError on error. * * f = File.new("out", "w") * f.syswrite("ABCDEF") #=> 6 */ static VALUE rb_io_syswrite(io, str) VALUE io, str; { OpenFile *fptr; FILE *f; long n; rb_secure(4); if (TYPE(str) != T_STRING) str = rb_obj_as_string(str); GetOpenFile(io, fptr); rb_io_check_writable(fptr); f = GetWriteFile(fptr); if (fptr->mode & FMODE_WBUF) { rb_warn("syswrite for buffered IO"); } if (!rb_thread_fd_writable(fileno(f))) { rb_io_check_closed(fptr); } TRAP_BEG; n = write(fileno(f), RSTRING(str)->ptr, RSTRING(str)->len); TRAP_END; if (n == -1) rb_sys_fail(fptr->path); return LONG2FIX(n); } /* * call-seq: * ios.sysread(integer ) => string * * Reads integer bytes from ios using a low-level * read and returns them as a string. Do not mix with other methods * that read from ios or you may get unpredictable results. * Raises SystemCallError on error and * EOFError at end of file. * * f = File.new("testfile") * f.sysread(16) #=> "This is line one" */ static VALUE rb_io_sysread(argc, argv, io) int argc; VALUE *argv; VALUE io; { VALUE len, str; OpenFile *fptr; long n, ilen; rb_scan_args(argc, argv, "11", &len, &str); ilen = NUM2LONG(len); if (NIL_P(str)) { str = rb_str_new(0, ilen); } else { StringValue(str); rb_str_modify(str); rb_str_resize(str, ilen); } if (ilen == 0) return str; GetOpenFile(io, fptr); rb_io_check_readable(fptr); if (READ_DATA_BUFFERED(fptr->f)) { rb_raise(rb_eIOError, "sysread for buffered IO"); } rb_str_locktmp(str); n = fileno(fptr->f); rb_thread_wait_fd(fileno(fptr->f)); rb_io_check_closed(fptr); if (RSTRING(str)->len != ilen) { rb_raise(rb_eRuntimeError, "buffer string modified"); } TRAP_BEG; n = read(fileno(fptr->f), RSTRING(str)->ptr, ilen); TRAP_END; rb_str_unlocktmp(str); if (n == -1) { rb_sys_fail(fptr->path); } rb_str_resize(str, n); if (n == 0 && ilen > 0) { rb_eof_error(); } RSTRING(str)->len = n; RSTRING(str)->ptr[n] = '\0'; OBJ_TAINT(str); return str; } /* * call-seq: * ios.binmode => ios * * Puts ios into binary mode. This is useful only in * MS-DOS/Windows environments. Once a stream is in binary mode, it * cannot be reset to nonbinary mode. */ VALUE rb_io_binmode(io) VALUE io; { #if defined(_WIN32) || defined(DJGPP) || defined(__CYGWIN__) || defined(__human68k__) || defined(__EMX__) OpenFile *fptr; GetOpenFile(io, fptr); #ifdef __human68k__ if (fptr->f) fmode(fptr->f, _IOBIN); if (fptr->f2) fmode(fptr->f2, _IOBIN); #else if (fptr->f && setmode(fileno(fptr->f), O_BINARY) == -1) rb_sys_fail(fptr->path); if (fptr->f2 && setmode(fileno(fptr->f2), O_BINARY) == -1) rb_sys_fail(fptr->path); #endif fptr->mode |= FMODE_BINMODE; #endif return io; } char* rb_io_flags_mode(flags) int flags; { #ifdef O_BINARY # define MODE_BINMODE(a,b) ((flags & FMODE_BINMODE) ? (b) : (a)) #else # define MODE_BINMODE(a,b) (a) #endif if (flags & FMODE_APPEND) { if ((flags & FMODE_READWRITE) == FMODE_READWRITE) { return MODE_BINMODE("a+", "ab+"); } return MODE_BINMODE("a", "ab"); } switch (flags & FMODE_READWRITE) { case FMODE_READABLE: return MODE_BINMODE("r", "rb"); case FMODE_WRITABLE: return MODE_BINMODE("w", "wb"); case FMODE_READWRITE: if (flags & FMODE_CREATE) { return MODE_BINMODE("w+", "wb+"); } return MODE_BINMODE("r+", "rb+"); } rb_raise(rb_eArgError, "illegal access modenum %o", flags); return NULL; /* not reached */ } int rb_io_mode_flags(mode) const char *mode; { int flags = 0; const char *m = mode; switch (*m++) { case 'r': flags |= FMODE_READABLE; break; case 'w': flags |= FMODE_WRITABLE | FMODE_CREATE; break; case 'a': flags |= FMODE_WRITABLE | FMODE_APPEND | FMODE_CREATE; break; default: error: rb_raise(rb_eArgError, "illegal access mode %s", mode); } while (*m) { switch (*m++) { case 'b': flags |= FMODE_BINMODE; break; case '+': flags |= FMODE_READWRITE; break; default: goto error; } } return flags; } int rb_io_modenum_flags(mode) int mode; { int flags = 0; switch (mode & (O_RDONLY|O_WRONLY|O_RDWR)) { case O_RDONLY: flags = FMODE_READABLE; break; case O_WRONLY: flags = FMODE_WRITABLE; break; case O_RDWR: flags = FMODE_READWRITE; break; } if (mode & O_APPEND) { flags |= FMODE_APPEND; } if (mode & O_CREAT) { flags |= FMODE_CREATE; } #ifdef O_BINARY if (mode & O_BINARY) { flags |= FMODE_BINMODE; } #endif return flags; } static int rb_io_mode_modenum(mode) const char *mode; { int flags = 0; const char *m = mode; switch (*m++) { case 'r': flags |= O_RDONLY; break; case 'w': flags |= O_WRONLY | O_CREAT | O_TRUNC; break; case 'a': flags |= O_WRONLY | O_CREAT | O_APPEND; break; default: error: rb_raise(rb_eArgError, "illegal access mode %s", mode); } while (*m) { switch (*m++) { case 'b': #ifdef O_BINARY flags |= O_BINARY; #endif break; case '+': flags = (flags & ~O_ACCMODE) | O_RDWR; break; default: goto error; } } return flags; } #define MODENUM_MAX 4 static char* rb_io_modenum_mode(flags) int flags; { #ifdef O_BINARY # define MODE_BINARY(a,b) ((flags & O_BINARY) ? (b) : (a)) #else # define MODE_BINARY(a,b) (a) #endif if (flags & O_APPEND) { if ((flags & O_RDWR) == O_RDWR) { return MODE_BINARY("a+", "ab+"); } return MODE_BINARY("a", "ab"); } switch (flags & (O_RDONLY|O_WRONLY|O_RDWR)) { case O_RDONLY: return MODE_BINARY("r", "rb"); case O_WRONLY: return MODE_BINARY("w", "wb"); case O_RDWR: return MODE_BINARY("r+", "rb+"); } rb_raise(rb_eArgError, "illegal access modenum %o", flags); return NULL; /* not reached */ } static int rb_sysopen(fname, flags, mode) char *fname; int flags; unsigned int mode; { int fd; fd = open(fname, flags, mode); if (fd < 0) { if (errno == EMFILE || errno == ENFILE) { rb_gc(); fd = open(fname, flags, mode); } if (fd < 0) { rb_sys_fail(fname); } } return fd; } FILE * rb_fopen(fname, mode) const char *fname; const char *mode; { FILE *file; file = fopen(fname, mode); if (!file) { if (errno == EMFILE || errno == ENFILE) { rb_gc(); file = fopen(fname, mode); } if (!file) { rb_sys_fail(fname); } } #ifdef USE_SETVBUF if (setvbuf(file, NULL, _IOFBF, 0) != 0) rb_warn("setvbuf() can't be honoured for %s", fname); #endif #ifdef __human68k__ fmode(file, _IOTEXT); #endif return file; } FILE * rb_fdopen(fd, mode) int fd; const char *mode; { FILE *file; #if defined(sun) errno = 0; #endif file = fdopen(fd, mode); if (!file) { #if defined(sun) if (errno == 0 || errno == EMFILE || errno == ENFILE) { #else if (errno == EMFILE || errno == ENFILE) { #endif rb_gc(); #if defined(sun) errno = 0; #endif file = fdopen(fd, mode); } if (!file) { #ifdef _WIN32 if (errno == 0) errno = EINVAL; #endif #if defined(sun) if (errno == 0) errno = EMFILE; #endif rb_sys_fail(0); } } #ifdef USE_SETVBUF if (setvbuf(file, NULL, _IOFBF, 0) != 0) rb_warn("setvbuf() can't be honoured (fd=%d)", fd); #endif return file; } static VALUE rb_file_open_internal(io, fname, mode) VALUE io; const char *fname, *mode; { OpenFile *fptr; MakeOpenFile(io, fptr); fptr->mode = rb_io_mode_flags(mode); fptr->path = strdup(fname); fptr->f = rb_fopen(fptr->path, rb_io_flags_mode(fptr->mode)); return io; } VALUE rb_file_open(fname, mode) const char *fname, *mode; { return rb_file_open_internal(io_alloc(rb_cFile), fname, mode); } static VALUE rb_file_sysopen_internal(io, fname, flags, mode) VALUE io; char *fname; int flags, mode; { OpenFile *fptr; int fd; char *m; MakeOpenFile(io, fptr); fptr->path = strdup(fname); m = rb_io_modenum_mode(flags); fptr->mode = rb_io_modenum_flags(flags); fd = rb_sysopen(fptr->path, flags, mode); fptr->f = rb_fdopen(fd, m); return io; } VALUE rb_file_sysopen(fname, flags, mode) const char *fname; int flags, mode; { return rb_file_sysopen_internal(io_alloc(rb_cFile), fname, flags, mode); } #if defined (_WIN32) || defined(DJGPP) || defined(__CYGWIN__) || defined(__human68k__) || defined(__VMS) static struct pipe_list { OpenFile *fptr; struct pipe_list *next; } *pipe_list; static void pipe_add_fptr(fptr) OpenFile *fptr; { struct pipe_list *list; list = ALLOC(struct pipe_list); list->fptr = fptr; list->next = pipe_list; pipe_list = list; } static void pipe_del_fptr(fptr) OpenFile *fptr; { struct pipe_list *list = pipe_list; struct pipe_list *tmp; if (list->fptr == fptr) { pipe_list = list->next; free(list); return; } while (list->next) { if (list->next->fptr == fptr) { tmp = list->next; list->next = list->next->next; free(tmp); return; } list = list->next; } } static void pipe_atexit _((void)) { struct pipe_list *list = pipe_list; struct pipe_list *tmp; while (list) { tmp = list->next; rb_io_fptr_finalize(list->fptr); list = tmp; } } static void pipe_finalize _((OpenFile *fptr,int)); static void pipe_finalize(fptr, noraise) OpenFile *fptr; int noraise; { #if !defined (__CYGWIN__) && !defined(_WIN32) extern VALUE rb_last_status; int status; if (fptr->f) { status = pclose(fptr->f); } if (fptr->f2) { status = pclose(fptr->f2); } fptr->f = fptr->f2 = 0; #if defined DJGPP status <<= 8; #endif rb_last_status = INT2FIX(status); #else fptr_finalize(fptr, noraise); #endif pipe_del_fptr(fptr); } #endif void rb_io_synchronized(fptr) OpenFile *fptr; { fptr->mode |= FMODE_SYNC; } void rb_io_unbuffered(fptr) OpenFile *fptr; { rb_io_synchronized(fptr); } static VALUE pipe_open(VALUE pstr, char *pname, char *mode); static VALUE pipe_open(pstr, pname, mode) VALUE pstr; char *pname, *mode; { int modef = rb_io_mode_flags(mode); OpenFile *fptr; #if defined(DJGPP) || defined(__human68k__) || defined(__VMS) FILE *f; #else int pid; #ifdef _WIN32 FILE *fpr, *fpw; #else int pr[2], pw[2]; #endif #endif volatile int doexec; if (!pname) pname = StringValueCStr(pstr); doexec = (strcmp("-", pname) != 0); #if defined(DJGPP) || defined(__human68k__) || defined(__VMS) || defined(_WIN32) if (!doexec) { rb_raise(rb_eNotImpError, "fork() function is unimplemented on this machine"); } #endif #if defined(DJGPP) || defined(__human68k__) || defined(__VMS) f = popen(pname, mode); if (!f) rb_sys_fail(pname); else { VALUE port = io_alloc(rb_cIO); MakeOpenFile(port, fptr); fptr->finalize = pipe_finalize; fptr->mode = modef; pipe_add_fptr(fptr); if (modef & FMODE_READABLE) fptr->f = f; if (modef & FMODE_WRITABLE) { if (fptr->f) fptr->f2 = f; else fptr->f = f; rb_io_synchronized(fptr); } return (VALUE)port; } #else #ifdef _WIN32 retry: pid = pipe_exec(pname, rb_io_mode_modenum(mode), &fpr, &fpw); if (pid == -1) { /* exec failed */ if (errno == EAGAIN) { rb_thread_sleep(1); goto retry; } rb_sys_fail(pname); } else { VALUE port = io_alloc(rb_cIO); MakeOpenFile(port, fptr); fptr->mode = modef; fptr->mode |= FMODE_SYNC; fptr->pid = pid; if (modef & FMODE_READABLE) { fptr->f = fpr; } if (modef & FMODE_WRITABLE) { if (fptr->f) fptr->f2 = fpw; else fptr->f = fpw; } fptr->finalize = pipe_finalize; pipe_add_fptr(fptr); return (VALUE)port; } #else if (((modef & FMODE_READABLE) && pipe(pr) == -1) || ((modef & FMODE_WRITABLE) && pipe(pw) == -1)) rb_sys_fail(pname); if (!doexec) { fflush(stdin); /* is it really needed? */ fflush(stdout); fflush(stderr); } retry: switch ((pid = fork())) { case 0: /* child */ if (modef & FMODE_READABLE) { close(pr[0]); if (pr[1] != 1) { dup2(pr[1], 1); close(pr[1]); } } if (modef & FMODE_WRITABLE) { close(pw[1]); if (pw[0] != 0) { dup2(pw[0], 0); close(pw[0]); } } if (doexec) { int fd; for (fd = 3; fd < NOFILE; fd++) close(fd); rb_proc_exec(pname); fprintf(stderr, "%s:%d: command not found: %s\n", ruby_sourcefile, ruby_sourceline, pname); _exit(127); } rb_io_synchronized(RFILE(orig_stdout)->fptr); rb_io_synchronized(RFILE(orig_stderr)->fptr); return Qnil; case -1: /* fork failed */ if (errno == EAGAIN) { rb_thread_sleep(1); goto retry; } else { int e = errno; if ((modef & FMODE_READABLE)) { close(pr[0]); close(pr[1]); } if ((modef & FMODE_WRITABLE)) { close(pw[0]); close(pw[1]); } errno = e; rb_sys_fail(pname); } break; default: /* parent */ if (pid < 0) rb_sys_fail(pname); else { VALUE port = io_alloc(rb_cIO); MakeOpenFile(port, fptr); fptr->mode = modef; fptr->mode |= FMODE_SYNC; fptr->pid = pid; if (modef & FMODE_READABLE) { close(pr[1]); fptr->f = rb_fdopen(pr[0], "r"); } if (modef & FMODE_WRITABLE) { FILE *f = rb_fdopen(pw[1], "w"); close(pw[0]); if (fptr->f) fptr->f2 = f; else fptr->f = f; } #if defined (__CYGWIN__) fptr->finalize = pipe_finalize; pipe_add_fptr(fptr); #endif return port; } } #endif #endif } /* * call-seq: * IO.popen(cmd_string, mode="r" ) => io * IO.popen(cmd_string, mode="r" ) {|io| block } => obj * * Runs the specified command string as a subprocess; the subprocess's * standard input and output will be connected to the returned * IO object. If cmd_string starts with a * ``-'', then a new instance of Ruby is started as the * subprocess. The default mode for the new file object is ``r'', but * mode may be set to any of the modes listed in the description * for class IO. * * If a block is given, Ruby will run the command as a child connected * to Ruby with a pipe. Ruby's end of the pipe will be passed as a * parameter to the block. * At the end of block, Ruby close the pipe and sets $?. * In this case IO::popen returns * the value of the block. * * If a block is given with a cmd_string of ``-'', * the block will be run in two separate processes: once in the parent, * and once in a child. The parent process will be passed the pipe * object as a parameter to the block, the child version of the block * will be passed nil, and the child's standard in and * standard out will be connected to the parent through the pipe. Not * available on all platforms. * * f = IO.popen("uname") * p f.readlines * puts "Parent is #{Process.pid}" * IO.popen ("date") { |f| puts f.gets } * IO.popen("-") {|f| $stderr.puts "#{Process.pid} is here, f is #{f}"} * p $? * * produces: * * ["Linux\n"] * Parent is 26166 * Wed Apr 9 08:53:52 CDT 2003 * 26169 is here, f is * 26166 is here, f is # * # */ static VALUE rb_io_s_popen(argc, argv, klass) int argc; VALUE *argv; VALUE klass; { char *mode; VALUE pname, pmode, port; if (rb_scan_args(argc, argv, "11", &pname, &pmode) == 1) { mode = "r"; } else if (FIXNUM_P(pmode)) { mode = rb_io_modenum_mode(FIX2INT(pmode)); } else { mode = rb_io_flags_mode(rb_io_mode_flags(StringValueCStr(pmode))); } SafeStringValue(pname); port = pipe_open(pname, 0, mode); if (NIL_P(port)) { /* child */ if (rb_block_given_p()) { rb_yield(Qnil); fflush(stdout); fflush(stderr); _exit(0); } return Qnil; } RBASIC(port)->klass = klass; if (rb_block_given_p()) { return rb_ensure(rb_yield, port, io_close, port); } return port; } static VALUE rb_open_file(argc, argv, io) int argc; VALUE *argv; VALUE io; { VALUE fname, vmode, perm; char *path, *mode; int flags; unsigned int fmode; rb_scan_args(argc, argv, "12", &fname, &vmode, &perm); SafeStringValue(fname); path = StringValueCStr(fname); if (FIXNUM_P(vmode) || !NIL_P(perm)) { if (FIXNUM_P(vmode)) { flags = FIX2INT(vmode); } else { SafeStringValue(vmode); flags = rb_io_mode_modenum(RSTRING(vmode)->ptr); } fmode = NIL_P(perm) ? 0666 : NUM2UINT(perm); rb_file_sysopen_internal(io, path, flags, fmode); } else { mode = NIL_P(vmode) ? "r" : StringValueCStr(vmode); rb_file_open_internal(io, path, mode); } return io; } /* * call-seq: * IO.open(fd, mode_string="r" ) => io * IO.open(fd, mode_string="r" ) {|io| block } => obj * * With no associated block, open is a synonym for * IO::new. If the optional code block is given, it will * be passed io as an argument, and the IO object will * automatically be closed when the block terminates. In this instance, * IO::open returns the value of the block. * */ static VALUE rb_io_s_open(argc, argv, klass) int argc; VALUE *argv; VALUE klass; { VALUE io = rb_class_new_instance(argc, argv, klass); if (rb_block_given_p()) { return rb_ensure(rb_yield, io, io_close, io); } return io; } /* * call-seq: * IO.sysopen(path, [mode, [perm]]) => fixnum * * Opens the given path, returning the underlying file descriptor as a * Fixnum. * * IO.sysopen("testfile") #=> 3 * */ static VALUE rb_io_s_sysopen(argc, argv) int argc; VALUE *argv; { VALUE fname, vmode, perm; int flags, fd; unsigned int fmode; char *path; rb_scan_args(argc, argv, "12", &fname, &vmode, &perm); SafeStringValue(fname); if (NIL_P(vmode)) flags = O_RDONLY; else if (FIXNUM_P(vmode)) flags = FIX2INT(vmode); else { SafeStringValue(vmode); flags = rb_io_mode_modenum(RSTRING(vmode)->ptr); } if (NIL_P(perm)) fmode = 0666; else fmode = NUM2UINT(perm); path = ALLOCA_N(char, strlen(RSTRING(fname)->ptr)+1); strcpy(path, RSTRING(fname)->ptr); fd = rb_sysopen(path, flags, fmode); return INT2NUM(fd); } /* * call-seq: * open(path [, mode [, perm]] ) => io or nil * open(path [, mode [, perm]] ) {|io| block } => obj * * Creates an IO object connected to the given stream, * file, or subprocess. * * If path does not start with a pipe character * (``|''), treat it as the name of a file to open using * the specified mode (defaulting to ``r''). (See the table * of valid modes on page 331.) If a file is being created, its initial * permissions may be set using the integer third parameter. * * If a block is specified, it will be invoked with the * File object as a parameter, and the file will be * automatically closed when the block terminates. The call * returns the value of the block. * * If path starts with a pipe character, a subprocess is * created, connected to the caller by a pair of pipes. The returned * IO object may be used to write to the standard input * and read from the standard output of this subprocess. If the command * following the ``|'' is a single minus sign, Ruby forks, * and this subprocess is connected to the parent. In the subprocess, * the open call returns nil. If the command * is not ``-'', the subprocess runs the command. If a * block is associated with an open("|-") call, that block * will be run twice---once in the parent and once in the child. The * block parameter will be an IO object in the parent and * nil in the child. The parent's IO object * will be connected to the child's $stdin and * $stdout. The subprocess will be terminated at the end * of the block. * * open("testfile") do |f| * print f.gets * end * * produces: * * This is line one * * Open a subprocess and read its output: * * cmd = open("|date") * print cmd.gets * cmd.close * * produces: * * Wed Apr 9 08:56:31 CDT 2003 * * Open a subprocess running the same Ruby program: * * f = open("|-", "w+") * if f == nil * puts "in Child" * exit * else * puts "Got: #{f.gets}" * end * * produces: * * Got: in Child * * Open a subprocess using a block to receive the I/O object: * * open("|-") do |f| * if f == nil * puts "in Child" * else * puts "Got: #{f.gets}" * end * end * * produces: * * Got: in Child */ static VALUE rb_f_open(argc, argv) int argc; VALUE *argv; { if (argc >= 1) { char *str = StringValuePtr(argv[0]); if (str[0] == '|') { VALUE tmp = rb_str_new(str+1, RSTRING(argv[0])->len-1); OBJ_INFECT(tmp, argv[0]); argv[0] = tmp; return rb_io_s_popen(argc, argv, rb_cIO); } } return rb_io_s_open(argc, argv, rb_cFile); } static VALUE rb_io_open(fname, mode) char *fname, *mode; { if (fname[0] == '|') { return pipe_open(0, fname+1, mode); } else { return rb_file_open(fname, mode); } } static VALUE rb_io_get_io(io) VALUE io; { return rb_convert_type(io, T_FILE, "IO", "to_io"); } static VALUE rb_io_check_io(io) VALUE io; { return rb_check_convert_type(io, T_FILE, "IO", "to_io"); } static char* rb_io_mode_string(fptr) OpenFile *fptr; { switch (fptr->mode & FMODE_READWRITE) { case FMODE_READABLE: default: return "r"; case FMODE_WRITABLE: return "w"; case FMODE_READWRITE: return "r+"; } } static VALUE io_reopen(io, nfile) VALUE io, nfile; { OpenFile *fptr, *orig; char *mode; int fd, fd2; off_t pos = 0; nfile = rb_io_get_io(nfile); if (rb_safe_level() >= 4 && (!OBJ_TAINTED(io) || !OBJ_TAINTED(nfile))) { rb_raise(rb_eSecurityError, "Insecure: can't reopen"); } GetOpenFile(io, fptr); GetOpenFile(nfile, orig); if (fptr == orig) return io; if (orig->mode & FMODE_READABLE) { pos = io_tell(orig); } if (orig->f2) { io_fflush(orig->f2, orig); } else if (orig->mode & FMODE_WRITABLE) { io_fflush(orig->f, orig); } if (fptr->mode & FMODE_WRITABLE) { io_fflush(GetWriteFile(fptr), fptr); } /* copy OpenFile structure */ fptr->mode = orig->mode; fptr->pid = orig->pid; fptr->lineno = orig->lineno; if (fptr->path) free(fptr->path); if (orig->path) fptr->path = strdup(orig->path); else fptr->path = 0; fptr->finalize = orig->finalize; mode = rb_io_mode_string(fptr); fd = fileno(fptr->f); fd2 = fileno(orig->f); if (fd != fd2) { if (fptr->f == stdin || fptr->f == stdout || fptr->f == stderr) { clearerr(fptr->f); /* need to keep stdio objects */ if (dup2(fd2, fd) < 0) rb_sys_fail(orig->path); } else { FILE *f2 = fptr->f2; int m = fptr->mode; fclose(fptr->f); fptr->f = f2; fptr->f2 = NULL; fptr->mode &= (m & FMODE_READABLE) ? ~FMODE_READABLE : ~FMODE_WRITABLE; if (dup2(fd2, fd) < 0) rb_sys_fail(orig->path); if (f2) { fptr->f = rb_fdopen(fd, "r"); fptr->f2 = f2; } else { fptr->f = rb_fdopen(fd, mode); } fptr->mode = m; } rb_thread_fd_close(fd); if ((orig->mode & FMODE_READABLE) && pos >= 0) { io_seek(fptr, pos, SEEK_SET); io_seek(orig, pos, SEEK_SET); } } if (fptr->f2 && fd != fileno(fptr->f2)) { fd = fileno(fptr->f2); if (!orig->f2) { fclose(fptr->f2); rb_thread_fd_close(fd); fptr->f2 = 0; } else if (fd != (fd2 = fileno(orig->f2))) { fclose(fptr->f2); rb_thread_fd_close(fd); if (dup2(fd2, fd) < 0) rb_sys_fail(orig->path); fptr->f2 = rb_fdopen(fd, "w"); } } if (fptr->mode & FMODE_BINMODE) { rb_io_binmode(io); } RBASIC(io)->klass = RBASIC(nfile)->klass; return io; } /* * call-seq: * ios.reopen(other_IO) => ios * ios.reopen(path, mode_str) => ios * * Reassociates ios with the I/O stream given in * other_IO or to a new stream opened on path. This may * dynamically change the actual class of this stream. * * f1 = File.new("testfile") * f2 = File.new("testfile") * f2.readlines[0] #=> "This is line one\n" * f2.reopen(f1) #=> # * f2.readlines[0] #=> "This is line one\n" */ static VALUE rb_io_reopen(argc, argv, file) int argc; VALUE *argv; VALUE file; { VALUE fname, nmode; char *mode; OpenFile *fptr; rb_secure(4); if (rb_scan_args(argc, argv, "11", &fname, &nmode) == 1) { VALUE tmp = rb_io_check_io(fname); if (!NIL_P(tmp)) { return io_reopen(file, tmp); } } SafeStringValue(fname); rb_io_taint_check(file); fptr = RFILE(file)->fptr; if (!fptr) { fptr = RFILE(file)->fptr = ALLOC(OpenFile); MEMZERO(fptr, OpenFile, 1); } if (!NIL_P(nmode)) { fptr->mode = rb_io_mode_flags(StringValueCStr(nmode)); } if (fptr->path) { free(fptr->path); fptr->path = 0; } fptr->path = strdup(StringValueCStr(fname)); mode = rb_io_flags_mode(fptr->mode); if (!fptr->f) { fptr->f = rb_fopen(fptr->path, mode); if (fptr->f2) { fclose(fptr->f2); fptr->f2 = 0; } return file; } if (freopen(fptr->path, mode, fptr->f) == 0) { rb_sys_fail(fptr->path); } #ifdef USE_SETVBUF if (setvbuf(fptr->f, NULL, _IOFBF, 0) != 0) rb_warn("setvbuf() can't be honoured for %s", fptr->path); #endif if (fptr->f2) { if (freopen(fptr->path, "w", fptr->f2) == 0) { rb_sys_fail(fptr->path); } } return file; } /* :nodoc: */ static VALUE rb_io_init_copy(dest, io) VALUE dest, io; { OpenFile *fptr, *orig; int fd; char *mode; io = rb_io_get_io(io); if (dest == io) return dest; GetOpenFile(io, orig); MakeOpenFile(dest, fptr); if (orig->f2) { io_fflush(orig->f2, orig); fseeko(orig->f, 0L, SEEK_CUR); } else if (orig->mode & FMODE_WRITABLE) { io_fflush(orig->f, orig); } else { fseeko(orig->f, 0L, SEEK_CUR); } /* copy OpenFile structure */ fptr->mode = orig->mode; fptr->pid = orig->pid; fptr->lineno = orig->lineno; if (orig->path) fptr->path = strdup(orig->path); fptr->finalize = orig->finalize; switch (fptr->mode & FMODE_READWRITE) { case FMODE_READABLE: default: mode = "r"; break; case FMODE_WRITABLE: mode = "w"; break; case FMODE_READWRITE: if (orig->f2) mode = "r"; else mode = "r+"; break; } fd = ruby_dup(fileno(orig->f)); fptr->f = rb_fdopen(fd, mode); fseeko(fptr->f, ftello(orig->f), SEEK_SET); if (orig->f2) { if (fileno(orig->f) != fileno(orig->f2)) { fd = ruby_dup(fileno(orig->f2)); } fptr->f2 = rb_fdopen(fd, "w"); fseeko(fptr->f2, ftello(orig->f2), SEEK_SET); } if (fptr->mode & FMODE_BINMODE) { rb_io_binmode(dest); } return dest; } /* * call-seq: * ios.printf(format_string [, obj, ...] ) => nil * * Formats and writes to ios, converting parameters under * control of the format string. See Kernel#sprintf * for details. */ VALUE rb_io_printf(argc, argv, out) int argc; VALUE argv[]; VALUE out; { rb_io_write(out, rb_f_sprintf(argc, argv)); return Qnil; } /* * call-seq: * printf(io, string [, obj ... ] ) => nil * printf(string [, obj ... ] ) => nil * * Equivalent to: * io.write(sprintf(string, obj, ...) * or * $stdout.write(sprintf(string, obj, ...) */ static VALUE rb_f_printf(argc, argv) int argc; VALUE argv[]; { VALUE out; if (argc == 0) return Qnil; if (TYPE(argv[0]) == T_STRING) { out = rb_stdout; } else { out = argv[0]; argv++; argc--; } rb_io_write(out, rb_f_sprintf(argc, argv)); return Qnil; } /* * call-seq: * ios.print() => nil * ios.print(obj, ...) => nil * * Writes the given object(s) to ios. The stream must be * opened for writing. If the output record separator ($\\) * is not nil, it will be appended to the output. If no * arguments are given, prints $_. Objects that aren't * strings will be converted by calling their to_s method. * With no argument, prints the contents of the variable $_. * Returns nil. * * $stdout.print("This is ", 100, " percent.\n") * * produces: * * This is 100 percent. */ VALUE rb_io_print(argc, argv, out) int argc; VALUE *argv; VALUE out; { int i; VALUE line; /* if no argument given, print `$_' */ if (argc == 0) { argc = 1; line = rb_lastline_get(); argv = &line; } for (i=0; i0) { rb_io_write(out, rb_output_fs); } switch (TYPE(argv[i])) { case T_NIL: rb_io_write(out, rb_str_new2("nil")); break; default: rb_io_write(out, argv[i]); break; } } if (!NIL_P(rb_output_rs)) { rb_io_write(out, rb_output_rs); } return Qnil; } /* * call-seq: * print(obj, ...) => nil * * Prints each object in turn to $stdout. If the output * field separator ($,) is not +nil+, its * contents will appear between each field. If the output record * separator ($\\) is not +nil+, it will be * appended to the output. If no arguments are given, prints * $_. Objects that aren't strings will be converted by * calling their to_s method. * * print "cat", [1,2,3], 99, "\n" * $, = ", " * $\ = "\n" * print "cat", [1,2,3], 99 * * produces: * * cat12399 * cat, 1, 2, 3, 99 */ static VALUE rb_f_print(argc, argv) int argc; VALUE *argv; { rb_io_print(argc, argv, rb_stdout); return Qnil; } /* * call-seq: * ios.putc(obj) => obj * * If obj is Numeric, write the character whose * code is obj, otherwise write the first character of the * string representation of obj to ios. * * $stdout.putc "A" * $stdout.putc 65 * * produces: * * AA */ static VALUE rb_io_putc(io, ch) VALUE io, ch; { char c = NUM2CHR(ch); rb_io_write(io, rb_str_new(&c, 1)); return ch; } /* * call-seq: * putc(int) => int * * Equivalent to: * * $stdout.putc(int) */ static VALUE rb_f_putc(recv, ch) VALUE recv, ch; { return rb_io_putc(rb_stdout, ch); } static VALUE io_puts_ary(ary, out) VALUE ary, out; { VALUE tmp; long i; for (i=0; ilen; i++) { tmp = RARRAY(ary)->ptr[i]; if (rb_inspecting_p(tmp)) { tmp = rb_str_new2("[...]"); } rb_io_puts(1, &tmp, out); } return Qnil; } /* * call-seq: * ios.puts(obj, ...) => nil * * Writes the given objects to ios as with * IO#print. Writes a record separator (typically a * newline) after any that do not already end with a newline sequence. * If called with an array argument, writes each element on a new line. * If called without arguments, outputs a single record separator. * * $stdout.puts("this", "is", "a", "test") * * produces: * * this * is * a * test */ VALUE rb_io_puts(argc, argv, out) int argc; VALUE *argv; VALUE out; { int i; VALUE line; /* if no argument given, print newline. */ if (argc == 0) { rb_io_write(out, rb_default_rs); return Qnil; } for (i=0; ilen == 0 || RSTRING(line)->ptr[RSTRING(line)->len-1] != '\n') { rb_io_write(out, rb_default_rs); } } return Qnil; } /* * call-seq: * puts(obj, ...) => nil * * Equivalent to * * $stdout.puts(obj, ...) */ static VALUE rb_f_puts(argc, argv) int argc; VALUE *argv; { rb_io_puts(argc, argv, rb_stdout); return Qnil; } void rb_p(obj) /* for debug print within C code */ VALUE obj; { rb_io_write(rb_stdout, rb_obj_as_string(rb_inspect(obj))); rb_io_write(rb_stdout, rb_default_rs); } /* * call-seq: * p(obj, ...) => nil * * For each object, directly writes * _obj_.+inspect+ followed by the current output * record separator to the program's standard output. * * S = Struct.new(:name, :state) * s = S['dave', 'TX'] * p s * * produces: * * # */ static VALUE rb_f_p(argc, argv) int argc; VALUE *argv; { int i; for (i=0; i) => nil * * Prints obj on the given port (default $>). * Equivalent to: * * def display(port=$>) * port.write self * end * * For example: * * 1.display * "cat".display * [ 4, 5, 6 ].display * puts * * produces: * * 1cat456 */ static VALUE rb_obj_display(argc, argv, self) int argc; VALUE *argv; VALUE self; { VALUE out; if (rb_scan_args(argc, argv, "01", &out) == 0) { out = rb_stdout; } rb_io_write(out, self); return Qnil; } void rb_write_error2(mesg, len) const char *mesg; long len; { rb_io_write(rb_stderr, rb_str_new(mesg, len)); } void rb_write_error(mesg) const char *mesg; { rb_write_error2(mesg, strlen(mesg)); } static void must_respond_to(mid, val, id) ID mid; VALUE val; ID id; { if (!rb_respond_to(val, mid)) { rb_raise(rb_eTypeError, "%s must have %s method, %s given", rb_id2name(id), rb_id2name(mid), rb_obj_classname(val)); } } static void stdout_setter(val, id, variable) VALUE val; ID id; VALUE *variable; { must_respond_to(id_write, val, id); *variable = val; } static void defout_setter(val, id, variable) VALUE val; ID id; VALUE *variable; { stdout_setter(val, id, variable); rb_warn("$defout is obsolete; use $stdout instead"); } static void deferr_setter(val, id, variable) VALUE val; ID id; VALUE *variable; { stdout_setter(val, id, variable); rb_warn("$deferr is obsolete; use $stderr instead"); } static VALUE prep_stdio(f, mode, klass) FILE *f; int mode; VALUE klass; { OpenFile *fp; VALUE io = io_alloc(klass); MakeOpenFile(io, fp); #ifdef __CYGWIN__ if (!isatty(fileno(f))) { mode |= O_BINARY; setmode(fileno(f), O_BINARY); } #endif fp->f = f; fp->mode = mode; return io; } static void prep_path(io, path) VALUE io; char *path; { OpenFile *fptr; GetOpenFile(io, fptr); if (fptr->path) rb_bug("illegal prep_path() call"); fptr->path = strdup(path); } /* * call-seq: * IO.new(fd, mode) => io * * Returns a new IO object (a stream) for the given * integer file descriptor and mode string. See also * IO#fileno and IO::for_fd. * * a = IO.new(2,"w") # '2' is standard error * $stderr.puts "Hello" * a.puts "World" * * produces: * * Hello * World */ static VALUE rb_io_initialize(argc, argv, io) int argc; VALUE *argv; VALUE io; { VALUE fnum, mode; OpenFile *fp; int fd, flags; rb_secure(4); rb_scan_args(argc, argv, "11", &fnum, &mode); fd = NUM2INT(fnum); if (argc == 2) { if (FIXNUM_P(mode)) { flags = FIX2LONG(mode); } else { SafeStringValue(mode); flags = rb_io_mode_modenum(StringValueCStr(mode)); } } else { #if defined(HAVE_FCNTL) && defined(F_GETFL) flags = fcntl(fd, F_GETFL); if (flags == -1) rb_sys_fail(0); #else flags = O_RDONLY; #endif } MakeOpenFile(io, fp); fp->mode = rb_io_modenum_flags(flags); fp->f = rb_fdopen(fd, rb_io_modenum_mode(flags)); return io; } /* * call-seq: * File.new(filename, mode="r") => file * File.new(filename [, mode [, perm]]) => file * * Opens the file named by _filename_ according to * _mode_ (default is ``r'') and returns a new * File object. See the description of class +IO+ for * a description of _mode_. The file mode may optionally be * specified as a +Fixnum+ by _or_-ing together the * flags (O_RDONLY etc, again described under +IO+). Optional * permission bits may be given in _perm_. These mode and permission * bits are platform dependent; on Unix systems, see * open(2) for details. * * f = File.new("testfile", "r") * f = File.new("newfile", "w+") * f = File.new("newfile", File::CREAT|File::TRUNC|File::RDWR, 0644) */ static VALUE rb_file_initialize(argc, argv, io) int argc; VALUE *argv; VALUE io; { if (RFILE(io)->fptr) { rb_raise(rb_eRuntimeError, "reinitializing File"); } if (0 < argc && argc < 3) { VALUE fd = rb_check_convert_type(argv[0], T_FIXNUM, "Fixnum", "to_int"); if (!NIL_P(fd)) { argv[0] = fd; return rb_io_initialize(argc, argv, io); } } rb_open_file(argc, argv, io); return io; } /* * call-seq: * IO.new(fd, mode_string) => io * * Returns a new IO object (a stream) for the given * integer file descriptor and mode string. See also * IO#fileno and IO::for_fd. * * a = IO.new(2,"w") # '2' is standard error * $stderr.puts "Hello" * a.puts "World" * * produces: * * Hello * World */ static VALUE rb_io_s_new(argc, argv, klass) int argc; VALUE *argv; VALUE klass; { if (rb_block_given_p()) { char *cname = rb_class2name(klass); rb_warn("%s::new() does not take block; use %s::open() instead", cname, cname); } return rb_class_new_instance(argc, argv, klass); } /* * call-seq: * IO.for_fd(fd, mode) => io * * Synonym for IO::new. * */ static VALUE rb_io_s_for_fd(argc, argv, klass) int argc; VALUE *argv; VALUE klass; { VALUE io = rb_obj_alloc(klass); rb_io_initialize(argc, argv, io); return io; } static int binmode = 0; static VALUE argf_forward(int argc, VALUE *argv) { return rb_funcall3(current_file, ruby_frame->last_func, argc, argv); } #define ARGF_FORWARD(argc, argv) do {\ if (TYPE(current_file) != T_FILE)\ return argf_forward(argc, argv);\ } while (0) #define NEXT_ARGF_FORWARD(argc, argv) do {\ if (!next_argv()) return Qnil;\ ARGF_FORWARD(argc, argv);\ } while (0) static void argf_close(file) VALUE file; { if (TYPE(file) == T_FILE) rb_io_close(file); else rb_funcall3(file, rb_intern("close"), 0, 0); } static int next_argv() { extern VALUE rb_argv; char *fn; OpenFile *fptr; int stdout_binmode = 0; if (TYPE(rb_stdout) == T_FILE) { GetOpenFile(rb_stdout, fptr); if (fptr->mode & FMODE_BINMODE) stdout_binmode = 1; } if (init_p == 0) { if (RARRAY(rb_argv)->len > 0) { next_p = 1; } else { next_p = -1; } init_p = 1; gets_lineno = 0; } if (next_p == 1) { next_p = 0; retry: if (RARRAY(rb_argv)->len > 0) { filename = rb_ary_shift(rb_argv); fn = StringValueCStr(filename); if (strlen(fn) == 1 && fn[0] == '-') { current_file = rb_stdin; if (ruby_inplace_mode) { rb_warn("Can't do inplace edit for stdio; skipping"); goto retry; } } else { FILE *fr = rb_fopen(fn, "r"); if (ruby_inplace_mode) { struct stat st, st2; VALUE str; FILE *fw; if (TYPE(rb_stdout) == T_FILE && rb_stdout != orig_stdout) { rb_io_close(rb_stdout); } fstat(fileno(fr), &st); if (*ruby_inplace_mode) { str = rb_str_new2(fn); #ifdef NO_LONG_FNAME ruby_add_suffix(str, ruby_inplace_mode); #else rb_str_cat2(str, ruby_inplace_mode); #endif #ifdef NO_SAFE_RENAME (void)fclose(fr); (void)unlink(RSTRING(str)->ptr); (void)rename(fn, RSTRING(str)->ptr); fr = rb_fopen(RSTRING(str)->ptr, "r"); #else if (rename(fn, RSTRING(str)->ptr) < 0) { rb_warn("Can't rename %s to %s: %s, skipping file", fn, RSTRING(str)->ptr, strerror(errno)); fclose(fr); goto retry; } #endif } else { #ifdef NO_SAFE_RENAME rb_fatal("Can't do inplace edit without backup"); #else if (unlink(fn) < 0) { rb_warn("Can't remove %s: %s, skipping file", fn, strerror(errno)); fclose(fr); goto retry; } #endif } fw = rb_fopen(fn, "w"); #ifndef NO_SAFE_RENAME fstat(fileno(fw), &st2); #ifdef HAVE_FCHMOD fchmod(fileno(fw), st.st_mode); #else chmod(fn, st.st_mode); #endif if (st.st_uid!=st2.st_uid || st.st_gid!=st2.st_gid) { fchown(fileno(fw), st.st_uid, st.st_gid); } #endif rb_stdout = prep_stdio(fw, FMODE_WRITABLE, rb_cFile); prep_path(rb_stdout, fn); if (stdout_binmode) rb_io_binmode(rb_stdout); } current_file = prep_stdio(fr, FMODE_READABLE, rb_cFile); prep_path(current_file, fn); } if (binmode) rb_io_binmode(current_file); } else { next_p = 1; return Qfalse; } } else if (next_p == -1) { current_file = rb_stdin; filename = rb_str_new2("-"); if (ruby_inplace_mode) { rb_warn("Can't do inplace edit for stdio"); rb_stdout = orig_stdout; } } return Qtrue; } static VALUE argf_getline(argc, argv) int argc; VALUE *argv; { VALUE line; retry: if (!next_argv()) return Qnil; if (argc == 0 && rb_rs == rb_default_rs) { line = rb_io_gets(current_file); } else { VALUE rs; if (argc == 0) { rs = rb_rs; } else { rb_scan_args(argc, argv, "1", &rs); if (!NIL_P(rs)) StringValue(rs); } line = rb_io_getline(rs, current_file); } if (NIL_P(line) && next_p != -1) { argf_close(current_file); next_p = 1; goto retry; } if (!NIL_P(line)) { gets_lineno++; lineno = INT2FIX(gets_lineno); } return line; } /* * call-seq: * gets(separator=$/) => string or nil * * Returns (and assigns to $_) the next line from the list * of files in +ARGV+ (or $*), or from standard * input if no files are present on the command line. Returns * +nil+ at end of file. The optional argument specifies the * record separator. The separator is included with the contents of * each record. A separator of +nil+ reads the entire * contents, and a zero-length separator reads the input one paragraph * at a time, where paragraphs are divided by two consecutive newlines. * If multiple filenames are present in +ARGV+, * +gets(nil)+ will read the contents one file at a time. * * ARGV << "testfile" * print while gets * * produces: * * This is line one * This is line two * This is line three * And so on... * * The style of programming using $_ as an implicit * parameter is gradually losing favor in the Ruby community. */ static VALUE rb_f_gets(argc, argv) int argc; VALUE *argv; { VALUE line; if (!next_argv()) return Qnil; if (TYPE(current_file) != T_FILE) { line = rb_funcall3(current_file, rb_intern("gets"), argc, argv); } else { line = argf_getline(argc, argv); } rb_lastline_set(line); return line; } VALUE rb_gets() { VALUE line; if (rb_rs != rb_default_rs) { return rb_f_gets(0, 0); } retry: if (!next_argv()) return Qnil; line = rb_io_gets(current_file); if (NIL_P(line) && next_p != -1) { argf_close(current_file); next_p = 1; goto retry; } rb_lastline_set(line); if (!NIL_P(line)) { gets_lineno++; lineno = INT2FIX(gets_lineno); } return line; } /* * call-seq: * readline(separator=$/) => string * * Equivalent to Kernel::gets, except * +readline+ raises +EOFError+ at end of file. */ static VALUE rb_f_readline(argc, argv) int argc; VALUE *argv; { VALUE line; if (!next_argv()) rb_eof_error(); ARGF_FORWARD(argc, argv); line = rb_f_gets(argc, argv); if (NIL_P(line)) { rb_eof_error(); } return line; } /* * obsolete */ static VALUE rb_f_getc() { rb_warn("getc is obsolete; use STDIN.getc instead"); if (TYPE(rb_stdin) != T_FILE) { return rb_funcall3(rb_stdin, rb_intern("getc"), 0, 0); } return rb_io_getc(rb_stdin); } /* * call-seq: * readlines(separator=$/) => array * * Returns an array containing the lines returned by calling * Kernel.gets(separator) until the end of file. */ static VALUE rb_f_readlines(argc, argv) int argc; VALUE *argv; { VALUE line, ary; NEXT_ARGF_FORWARD(argc, argv); ary = rb_ary_new(); while (!NIL_P(line = argf_getline(argc, argv))) { rb_ary_push(ary, line); } return ary; } /* * call-seq: * `cmd` => string * * Returns the standard output of running _cmd_ in a subshell. * The built-in syntax %x{...} uses * this method. Sets $? to the process status. * * `date` #=> "Wed Apr 9 08:56:30 CDT 2003\n" * `ls testdir`.split[1] #=> "main.rb" * `echo oops && exit 99` #=> "oops\n" * $?.exitstatus #=> 99 */ static VALUE rb_f_backquote(obj, str) VALUE obj, str; { volatile VALUE port; VALUE result; OpenFile *fptr; SafeStringValue(str); port = pipe_open(str, 0, "r"); if (NIL_P(port)) return rb_str_new(0,0); GetOpenFile(port, fptr); result = read_all(fptr, remain_size(fptr), Qnil); rb_io_close(port); return result; } #ifdef HAVE_SYS_SELECT_H #include #endif /* * call-seq: * IO.select(read_array * [, write_array * [, error_array * [, timeout]]] ) => array or nil * * See Kernel#select. */ static VALUE rb_f_select(argc, argv, obj) int argc; VALUE *argv; VALUE obj; { VALUE read, write, except, timeout, res, list; fd_set rset, wset, eset, pset; fd_set *rp, *wp, *ep; struct timeval *tp, timerec; OpenFile *fptr; long i; int max = 0, n; int interrupt_flag = 0; int pending = 0; rb_scan_args(argc, argv, "13", &read, &write, &except, &timeout); if (NIL_P(timeout)) { tp = 0; } else { timerec = rb_time_interval(timeout); tp = &timerec; } FD_ZERO(&pset); if (!NIL_P(read)) { Check_Type(read, T_ARRAY); rp = &rset; FD_ZERO(rp); for (i=0; ilen; i++) { GetOpenFile(rb_io_get_io(RARRAY(read)->ptr[i]), fptr); FD_SET(fileno(fptr->f), rp); if (READ_DATA_PENDING(fptr->f)) { /* check for buffered data */ pending++; FD_SET(fileno(fptr->f), &pset); } if (max < fileno(fptr->f)) max = fileno(fptr->f); } if (pending) { /* no blocking if there's buffered data */ timerec.tv_sec = timerec.tv_usec = 0; tp = &timerec; } } else rp = 0; if (!NIL_P(write)) { Check_Type(write, T_ARRAY); wp = &wset; FD_ZERO(wp); for (i=0; ilen; i++) { GetOpenFile(rb_io_get_io(RARRAY(write)->ptr[i]), fptr); FD_SET(fileno(fptr->f), wp); if (max < fileno(fptr->f)) max = fileno(fptr->f); if (fptr->f2) { FD_SET(fileno(fptr->f2), wp); if (max < fileno(fptr->f2)) max = fileno(fptr->f2); } } } else wp = 0; if (!NIL_P(except)) { Check_Type(except, T_ARRAY); ep = &eset; FD_ZERO(ep); for (i=0; ilen; i++) { GetOpenFile(rb_io_get_io(RARRAY(except)->ptr[i]), fptr); FD_SET(fileno(fptr->f), ep); if (max < fileno(fptr->f)) max = fileno(fptr->f); if (fptr->f2) { FD_SET(fileno(fptr->f2), ep); if (max < fileno(fptr->f2)) max = fileno(fptr->f2); } } } else { ep = 0; } max++; n = rb_thread_select(max, rp, wp, ep, tp); if (n < 0) { rb_sys_fail(0); } if (!pending && n == 0) return Qnil; /* returns nil on timeout */ res = rb_ary_new2(3); rb_ary_push(res, rp?rb_ary_new():rb_ary_new2(0)); rb_ary_push(res, wp?rb_ary_new():rb_ary_new2(0)); rb_ary_push(res, ep?rb_ary_new():rb_ary_new2(0)); if (interrupt_flag == 0) { if (rp) { list = RARRAY(res)->ptr[0]; for (i=0; i< RARRAY(read)->len; i++) { GetOpenFile(rb_io_get_io(RARRAY(read)->ptr[i]), fptr); if (FD_ISSET(fileno(fptr->f), rp) || FD_ISSET(fileno(fptr->f), &pset)) { rb_ary_push(list, rb_ary_entry(read, i)); } } } if (wp) { list = RARRAY(res)->ptr[1]; for (i=0; i< RARRAY(write)->len; i++) { GetOpenFile(rb_io_get_io(RARRAY(write)->ptr[i]), fptr); if (FD_ISSET(fileno(fptr->f), wp)) { rb_ary_push(list, rb_ary_entry(write, i)); } else if (fptr->f2 && FD_ISSET(fileno(fptr->f2), wp)) { rb_ary_push(list, rb_ary_entry(write, i)); } } } if (ep) { list = RARRAY(res)->ptr[2]; for (i=0; i< RARRAY(except)->len; i++) { GetOpenFile(rb_io_get_io(RARRAY(except)->ptr[i]), fptr); if (FD_ISSET(fileno(fptr->f), ep)) { rb_ary_push(list, rb_ary_entry(except, i)); } else if (fptr->f2 && FD_ISSET(fileno(fptr->f2), ep)) { rb_ary_push(list, rb_ary_entry(except, i)); } } } } return res; /* returns an empty array on interrupt */ } #if !defined(MSDOS) && !defined(__human68k__) static int io_cntl(fd, cmd, narg, io_p) int fd, cmd, io_p; long narg; { int retval; #ifdef HAVE_FCNTL TRAP_BEG; # if defined(__CYGWIN__) retval = io_p?ioctl(fd, cmd, (void*)narg):fcntl(fd, cmd, narg); # else retval = io_p?ioctl(fd, cmd, narg):fcntl(fd, cmd, narg); # endif TRAP_END; #else if (!io_p) { rb_notimplement(); } TRAP_BEG; retval = ioctl(fd, cmd, narg); TRAP_END; #endif return retval; } #endif static VALUE rb_io_ctl(io, req, arg, io_p) VALUE io, req, arg; int io_p; { #if !defined(MSDOS) && !defined(__human68k__) int cmd = NUM2ULONG(req); OpenFile *fptr; long len = 0; long narg = 0; int retval; rb_secure(2); if (NIL_P(arg) || arg == Qfalse) { narg = 0; } else if (FIXNUM_P(arg)) { narg = FIX2LONG(arg); } else if (arg == Qtrue) { narg = 1; } else { VALUE tmp = rb_check_string_type(arg); if (NIL_P(tmp)) { narg = NUM2LONG(arg); } else { arg = tmp; #ifdef IOCPARM_MASK #ifndef IOCPARM_LEN #define IOCPARM_LEN(x) (((x) >> 16) & IOCPARM_MASK) #endif #endif #ifdef IOCPARM_LEN len = IOCPARM_LEN(cmd); /* on BSDish systems we're safe */ #else len = 256; /* otherwise guess at what's safe */ #endif rb_str_modify(arg); if (len <= RSTRING(arg)->len) { len = RSTRING(arg)->len; } if (RSTRING(arg)->len < len) { rb_str_resize(arg, len+1); } RSTRING(arg)->ptr[len] = 17; /* a little sanity check here */ narg = (long)RSTRING(arg)->ptr; } } GetOpenFile(io, fptr); retval = io_cntl(fileno(fptr->f), cmd, narg, io_p); if (retval < 0) rb_sys_fail(fptr->path); if (TYPE(arg) == T_STRING && RSTRING(arg)->ptr[len] != 17) { rb_raise(rb_eArgError, "return value overflowed string"); } if (fptr->f2 && fileno(fptr->f) != fileno(fptr->f2)) { /* call on f2 too; ignore result */ io_cntl(fileno(fptr->f2), cmd, narg, io_p); } if (!io_p && cmd == F_SETFL) { if (narg & O_NONBLOCK) { fptr->mode |= FMODE_WSPLIT_INITIALIZED; fptr->mode &= ~FMODE_WSPLIT; } else { fptr->mode &= ~(FMODE_WSPLIT_INITIALIZED|FMODE_WSPLIT); } } return INT2NUM(retval); #else rb_notimplement(); return Qnil; /* not reached */ #endif } /* * call-seq: * ios.ioctl(integer_cmd, arg) => integer * * Provides a mechanism for issuing low-level commands to control or * query I/O devices. Arguments and results are platform dependent. If * arg is a number, its value is passed directly. If it is a * string, it is interpreted as a binary sequence of bytes. On Unix * platforms, see ioctl(2) for details. Not implemented on * all platforms. */ static VALUE rb_io_ioctl(argc, argv, io) int argc; VALUE *argv; VALUE io; { VALUE req, arg; rb_scan_args(argc, argv, "11", &req, &arg); return rb_io_ctl(io, req, arg, 1); } /* * call-seq: * ios.fcntl(integer_cmd, arg) => integer * * Provides a mechanism for issuing low-level commands to control or * query file-oriented I/O streams. Arguments and results are platform * dependent. If arg is a number, its value is passed * directly. If it is a string, it is interpreted as a binary sequence * of bytes (Array#pack might be a useful way to build this * string). On Unix platforms, see fcntl(2) for details. * Not implemented on all platforms. */ static VALUE rb_io_fcntl(argc, argv, io) int argc; VALUE *argv; VALUE io; { #ifdef HAVE_FCNTL VALUE req, arg; rb_scan_args(argc, argv, "11", &req, &arg); return rb_io_ctl(io, req, arg, 0); #else rb_notimplement(); return Qnil; /* not reached */ #endif } /* * call-seq: * syscall(fixnum [, args...]) => integer * * Calls the operating system function identified by _fixnum_, * passing in the arguments, which must be either +String+ * objects, or +Integer+ objects that ultimately fit within * a native +long+. Up to nine parameters may be passed (14 * on the Atari-ST). The function identified by _fixnum_ is system * dependent. On some Unix systems, the numbers may be obtained from a * header file called syscall.h. * * syscall 4, 1, "hello\n", 6 # '4' is write(2) on our box * * produces: * * hello */ static VALUE rb_f_syscall(argc, argv) int argc; VALUE *argv; { #if defined(HAVE_SYSCALL) && !defined(__CHECKER__) #ifdef atarist unsigned long arg[14]; /* yes, we really need that many ! */ #else unsigned long arg[8]; #endif int retval = -1; int i = 1; int items = argc - 1; /* This probably won't work on machines where sizeof(long) != sizeof(int) * or where sizeof(long) != sizeof(char*). But such machines will * not likely have syscall implemented either, so who cares? */ rb_secure(2); if (argc == 0) rb_raise(rb_eArgError, "too few arguments for syscall"); if (argc > sizeof(arg) / sizeof(arg[0])) rb_raise(rb_eArgError, "too many arguments for syscall"); arg[0] = NUM2LONG(argv[0]); argv++; while (items--) { VALUE v = rb_check_string_type(*argv); if (!NIL_P(v)) { StringValue(v); rb_str_modify(v); arg[i] = (unsigned long)StringValueCStr(v); } else { arg[i] = (unsigned long)NUM2LONG(*argv); } argv++; i++; } TRAP_BEG; switch (argc) { case 1: retval = syscall(arg[0]); break; case 2: retval = syscall(arg[0],arg[1]); break; case 3: retval = syscall(arg[0],arg[1],arg[2]); break; case 4: retval = syscall(arg[0],arg[1],arg[2],arg[3]); break; case 5: retval = syscall(arg[0],arg[1],arg[2],arg[3],arg[4]); break; case 6: retval = syscall(arg[0],arg[1],arg[2],arg[3],arg[4],arg[5]); break; case 7: retval = syscall(arg[0],arg[1],arg[2],arg[3],arg[4],arg[5],arg[6]); break; case 8: retval = syscall(arg[0],arg[1],arg[2],arg[3],arg[4],arg[5],arg[6], arg[7]); break; #ifdef atarist case 9: retval = syscall(arg[0],arg[1],arg[2],arg[3],arg[4],arg[5],arg[6], arg[7], arg[8]); break; case 10: retval = syscall(arg[0],arg[1],arg[2],arg[3],arg[4],arg[5],arg[6], arg[7], arg[8], arg[9]); break; case 11: retval = syscall(arg[0],arg[1],arg[2],arg[3],arg[4],arg[5],arg[6], arg[7], arg[8], arg[9], arg[10]); break; case 12: retval = syscall(arg[0],arg[1],arg[2],arg[3],arg[4],arg[5],arg[6], arg[7], arg[8], arg[9], arg[10], arg[11]); break; case 13: retval = syscall(arg[0],arg[1],arg[2],arg[3],arg[4],arg[5],arg[6], arg[7], arg[8], arg[9], arg[10], arg[11], arg[12]); break; case 14: retval = syscall(arg[0],arg[1],arg[2],arg[3],arg[4],arg[5],arg[6], arg[7], arg[8], arg[9], arg[10], arg[11], arg[12], arg[13]); break; #endif /* atarist */ } TRAP_END; if (retval < 0) rb_sys_fail(0); return INT2NUM(retval); #else rb_notimplement(); return Qnil; /* not reached */ #endif } static VALUE io_new_instance _((VALUE)); static VALUE io_new_instance(args) VALUE args; { return rb_class_new_instance(2, (VALUE*)args+1, *(VALUE*)args); } /* * call-seq: * IO.pipe -> array * * Creates a pair of pipe endpoints (connected to each other) and * returns them as a two-element array of IO objects: * [ read_file, write_file ]. Not * available on all platforms. * * In the example below, the two processes close the ends of the pipe * that they are not using. This is not just a cosmetic nicety. The * read end of a pipe will not generate an end of file condition if * there are any writers with the pipe still open. In the case of the * parent process, the rd.read will never return if it * does not first issue a wr.close. * * rd, wr = IO.pipe * * if fork * wr.close * puts "Parent got: <#{rd.read}>" * rd.close * Process.wait * else * rd.close * puts "Sending message to parent" * wr.write "Hi Dad" * wr.close * end * * produces: * * Sending message to parent * Parent got: */ static VALUE rb_io_s_pipe(klass) VALUE klass; { #ifndef __human68k__ int pipes[2], state; VALUE r, w, args[3]; #ifdef _WIN32 if (_pipe(pipes, 1024, O_BINARY) == -1) #else if (pipe(pipes) == -1) #endif rb_sys_fail(0); args[0] = klass; args[1] = INT2NUM(pipes[0]); args[2] = INT2FIX(O_RDONLY); r = rb_protect(io_new_instance, (VALUE)args, &state); if (state) { close(pipes[0]); close(pipes[1]); rb_jump_tag(state); } args[1] = INT2NUM(pipes[1]); args[2] = INT2FIX(O_WRONLY); w = rb_protect(io_new_instance, (VALUE)args, &state); if (state) { close(pipes[1]); if (!NIL_P(r)) rb_io_close(r); rb_jump_tag(state); } rb_io_synchronized(RFILE(w)->fptr); return rb_assoc_new(r, w); #else rb_notimplement(); return Qnil; /* not reached */ #endif } struct foreach_arg { int argc; VALUE sep; VALUE io; }; static VALUE io_s_foreach(arg) struct foreach_arg *arg; { VALUE str; while (!NIL_P(str = rb_io_getline(arg->sep, arg->io))) { rb_yield(str); } return Qnil; } /* * call-seq: * IO.foreach(name, sep_string=$/) {|line| block } => nil * * Executes the block for every line in the named I/O port, where lines * are separated by sep_string. * * IO.foreach("testfile") {|x| print "GOT ", x } * * produces: * * GOT This is line one * GOT This is line two * GOT This is line three * GOT And so on... */ static VALUE rb_io_s_foreach(argc, argv) int argc; VALUE *argv; { VALUE fname; struct foreach_arg arg; rb_scan_args(argc, argv, "11", &fname, &arg.sep); SafeStringValue(fname); if (argc == 1) { arg.sep = rb_default_rs; } else if (!NIL_P(arg.sep)) { StringValue(arg.sep); } arg.io = rb_io_open(StringValueCStr(fname), "r"); if (NIL_P(arg.io)) return Qnil; return rb_ensure(io_s_foreach, (VALUE)&arg, rb_io_close, arg.io); } static VALUE io_s_readlines(arg) struct foreach_arg *arg; { return rb_io_readlines(arg->argc, &arg->sep, arg->io); } /* * call-seq: * IO.readlines(name, sep_string=$/) => array * * Reads the entire file specified by name as individual * lines, and returns those lines in an array. Lines are separated by * sep_string. * * a = IO.readlines("testfile") * a[0] #=> "This is line one\n" * */ static VALUE rb_io_s_readlines(argc, argv, io) int argc; VALUE *argv; VALUE io; { VALUE fname; struct foreach_arg arg; rb_scan_args(argc, argv, "11", &fname, &arg.sep); SafeStringValue(fname); arg.argc = argc - 1; arg.io = rb_io_open(StringValueCStr(fname), "r"); if (NIL_P(arg.io)) return Qnil; return rb_ensure(io_s_readlines, (VALUE)&arg, rb_io_close, arg.io); } static VALUE io_s_read(arg) struct foreach_arg *arg; { return io_read(arg->argc, &arg->sep, arg->io); } /* * call-seq: * IO.read(name, [length [, offset]] ) => string * * Opens the file, optionally seeks to the given offset, then returns * length bytes (defaulting to the rest of the file). * read ensures the file is closed before returning. * * IO.read("testfile") #=> "This is line one\nThis is line two\nThis is line three\nAnd so on...\n" * IO.read("testfile", 20) #=> "This is line one\nThi" * IO.read("testfile", 20, 10) #=> "ne one\nThis is line " */ static VALUE rb_io_s_read(argc, argv, io) int argc; VALUE *argv; VALUE io; { VALUE fname, offset; struct foreach_arg arg; rb_scan_args(argc, argv, "12", &fname, &arg.sep, &offset); SafeStringValue(fname); arg.argc = argc ? 1 : 0; arg.io = rb_io_open(StringValueCStr(fname), "r"); if (NIL_P(arg.io)) return Qnil; if (!NIL_P(offset)) { rb_io_seek(arg.io, offset, SEEK_SET); } return rb_ensure(io_s_read, (VALUE)&arg, rb_io_close, arg.io); } static VALUE argf_tell() { if (!next_argv()) { rb_raise(rb_eArgError, "no stream to tell"); } ARGF_FORWARD(0, 0); return rb_io_tell(current_file); } static VALUE argf_seek_m(argc, argv, self) int argc; VALUE *argv; VALUE self; { if (!next_argv()) { rb_raise(rb_eArgError, "no stream to seek"); } ARGF_FORWARD(argc, argv); return rb_io_seek_m(argc, argv, current_file); } static VALUE argf_set_pos(self, offset) VALUE self, offset; { if (!next_argv()) { rb_raise(rb_eArgError, "no stream to set position"); } ARGF_FORWARD(1, &offset); return rb_io_set_pos(current_file, offset); } static VALUE argf_rewind() { if (!next_argv()) { rb_raise(rb_eArgError, "no stream to rewind"); } ARGF_FORWARD(0, 0); return rb_io_rewind(current_file); } static VALUE argf_fileno() { if (!next_argv()) { rb_raise(rb_eArgError, "no stream"); } ARGF_FORWARD(0, 0); return rb_io_fileno(current_file); } static VALUE argf_to_io() { next_argv(); ARGF_FORWARD(0, 0); return current_file; } static VALUE argf_eof() { if (current_file) { if (init_p == 0) return Qtrue; ARGF_FORWARD(0, 0); if (rb_io_eof(current_file)) { return Qtrue; } } return Qfalse; } static VALUE argf_read(argc, argv) int argc; VALUE *argv; { VALUE tmp, str, length; long len = 0; rb_scan_args(argc, argv, "02", &length, &str); if (!NIL_P(length)) { len = NUM2LONG(argv[0]); } if (!NIL_P(str)) { StringValue(str); rb_str_resize(str,0); argv[1] = Qnil; } retry: if (!next_argv()) { return str; } if (TYPE(current_file) != T_FILE) { tmp = argf_forward(argc, argv); } else { tmp = io_read(argc, argv, current_file); } if (NIL_P(str)) str = tmp; else if (!NIL_P(tmp)) rb_str_append(str, tmp); if (NIL_P(tmp) || NIL_P(length)) { if (next_p != -1) { argf_close(current_file); next_p = 1; goto retry; } } else if (argc >= 1) { if (RSTRING(str)->len < len) { len -= RSTRING(str)->len; argv[0] = INT2NUM(len); goto retry; } } return str; } static VALUE argf_getc() { VALUE byte; retry: if (!next_argv()) return Qnil; if (TYPE(current_file) != T_FILE) { byte = rb_funcall3(current_file, rb_intern("getc"), 0, 0); } else { byte = rb_io_getc(current_file); } if (NIL_P(byte) && next_p != -1) { argf_close(current_file); next_p = 1; goto retry; } return byte; } static VALUE argf_readchar() { VALUE c; NEXT_ARGF_FORWARD(0, 0); c = argf_getc(); if (NIL_P(c)) { rb_eof_error(); } return c; } static VALUE argf_each_line(argc, argv) int argc; VALUE *argv; { VALUE str; if (!next_argv()) return Qnil; if (TYPE(current_file) != T_FILE) { for (;;) { if (!next_argv()) return argf; rb_iterate(rb_each, current_file, rb_yield, 0); next_p = 1; } } while (!NIL_P(str = argf_getline(argc, argv))) { rb_yield(str); } return argf; } static VALUE argf_each_byte() { VALUE byte; while (!NIL_P(byte = argf_getc())) { rb_yield(byte); } return argf; } static VALUE argf_filename() { next_argv(); return filename; } static VALUE argf_file() { next_argv(); return current_file; } static VALUE argf_binmode() { binmode = 1; next_argv(); ARGF_FORWARD(0, 0); rb_io_binmode(current_file); return argf; } static VALUE argf_skip() { if (next_p != -1) { argf_close(current_file); next_p = 1; } return argf; } static VALUE argf_close_m() { next_argv(); argf_close(current_file); if (next_p != -1) { next_p = 1; } gets_lineno = 0; return argf; } static VALUE argf_closed() { next_argv(); ARGF_FORWARD(0, 0); return rb_io_closed(current_file); } static VALUE argf_to_s() { return rb_str_new2("ARGF"); } static VALUE opt_i_get() { if (!ruby_inplace_mode) return Qnil; return rb_str_new2(ruby_inplace_mode); } static void opt_i_set(val) VALUE val; { if (!RTEST(val)) { if (ruby_inplace_mode) free(ruby_inplace_mode); ruby_inplace_mode = 0; return; } StringValue(val); if (ruby_inplace_mode) free(ruby_inplace_mode); ruby_inplace_mode = 0; ruby_inplace_mode = strdup(StringValueCStr(val)); } /* * Class IO is the basis for all input and output in Ruby. * An I/O stream may be duplexed (that is, bidirectional), and * so may use more than one native operating system stream. * * Many of the examples in this section use class File, * the only standard subclass of IO. The two classes are * closely associated. * * As used in this section, portname may take any of the * following forms. * * * A plain string represents a filename suitable for the underlying * operating system. * * * A string starting with ``|'' indicates a subprocess. * The remainder of the string following the ``|'' is * invoked as a process with appropriate input/output channels * connected to it. * * * A string equal to ``|-'' will create another Ruby * instance as a subprocess. * * Ruby will convert pathnames between different operating system * conventions if possible. For instance, on a Windows system the * filename ``/gumby/ruby/test.rb'' will be opened as * ``\gumby\ruby\test.rb''. When specifying a * Windows-style filename in a Ruby string, remember to escape the * backslashes: * * "c:\\gumby\\ruby\\test.rb" * * Our examples here will use the Unix-style forward slashes; * File::SEPARATOR can be used to get the * platform-specific separator character. * * I/O ports may be opened in any one of several different modes, which * are shown in this section as mode. The mode may * either be a Fixnum or a String. If numeric, it should be * one of the operating system specific constants (O_RDONLY, * O_WRONLY, O_RDWR, O_APPEND and so on). See man open(2) for * more information. * * If the mode is given as a String, it must be one of the * values listed in the following table. * * Mode | Meaning * -----+-------------------------------------------------------- * "r" | Read-only, starts at beginning of file (default mode). * -----+-------------------------------------------------------- * "r+" | Read-write, starts at beginning of file. * -----+-------------------------------------------------------- * "w" | Write-only, truncates existing file * | to zero length or creates a new file for writing. * -----+-------------------------------------------------------- * "w+" | Read-write, truncates existing file to zero length * | or creates a new file for reading and writing. * -----+-------------------------------------------------------- * "a" | Write-only, starts at end of file if file exists, * | otherwise creates a new file for writing. * -----+-------------------------------------------------------- * "a+" | Read-write, starts at end of file if file exists, * | otherwise creates a new file for reading and * | writing. * -----+-------------------------------------------------------- * "b" | (DOS/Windows only) Binary file mode (may appear with * | any of the key letters listed above). * * * The global constant ARGF (also accessible as $<) provides an * IO-like stream which allows access to all files mentioned on the * command line (or STDIN if no files are mentioned). ARGF provides * the methods #path and #filename to access * the name of the file currently being read. */ void Init_IO() { #ifdef __CYGWIN__ #include static struct __cygwin_perfile pf[] = { {"", O_RDONLY | O_BINARY}, {"", O_WRONLY | O_BINARY}, {"", O_RDWR | O_BINARY}, {"", O_APPEND | O_BINARY}, {NULL, 0} }; cygwin_internal(CW_PERFILE, pf); #endif rb_eIOError = rb_define_class("IOError", rb_eStandardError); rb_eEOFError = rb_define_class("EOFError", rb_eIOError); id_write = rb_intern("write"); id_read = rb_intern("read"); id_getc = rb_intern("getc"); rb_define_global_function("syscall", rb_f_syscall, -1); rb_define_global_function("open", rb_f_open, -1); rb_define_global_function("printf", rb_f_printf, -1); rb_define_global_function("print", rb_f_print, -1); rb_define_global_function("putc", rb_f_putc, 1); rb_define_global_function("puts", rb_f_puts, -1); rb_define_global_function("gets", rb_f_gets, -1); rb_define_global_function("readline", rb_f_readline, -1); rb_define_global_function("getc", rb_f_getc, 0); rb_define_global_function("select", rb_f_select, -1); rb_define_global_function("readlines", rb_f_readlines, -1); rb_define_global_function("`", rb_f_backquote, 1); rb_define_global_function("p", rb_f_p, -1); rb_define_method(rb_mKernel, "display", rb_obj_display, -1); rb_cIO = rb_define_class("IO", rb_cObject); rb_include_module(rb_cIO, rb_mEnumerable); rb_define_alloc_func(rb_cIO, io_alloc); rb_define_singleton_method(rb_cIO, "new", rb_io_s_new, -1); rb_define_singleton_method(rb_cIO, "open", rb_io_s_open, -1); rb_define_singleton_method(rb_cIO, "sysopen", rb_io_s_sysopen, -1); rb_define_singleton_method(rb_cIO, "for_fd", rb_io_s_for_fd, -1); rb_define_singleton_method(rb_cIO, "popen", rb_io_s_popen, -1); rb_define_singleton_method(rb_cIO, "foreach", rb_io_s_foreach, -1); rb_define_singleton_method(rb_cIO, "readlines", rb_io_s_readlines, -1); rb_define_singleton_method(rb_cIO, "read", rb_io_s_read, -1); rb_define_singleton_method(rb_cIO, "select", rb_f_select, -1); rb_define_singleton_method(rb_cIO, "pipe", rb_io_s_pipe, 0); rb_define_method(rb_cIO, "initialize", rb_io_initialize, -1); rb_output_fs = Qnil; rb_define_hooked_variable("$,", &rb_output_fs, 0, rb_str_setter); rb_global_variable(&rb_default_rs); rb_rs = rb_default_rs = rb_str_new2("\n"); rb_output_rs = Qnil; OBJ_FREEZE(rb_default_rs); /* avoid modifying RS_default */ rb_define_hooked_variable("$/", &rb_rs, 0, rb_str_setter); rb_define_hooked_variable("$-0", &rb_rs, 0, rb_str_setter); rb_define_hooked_variable("$\\", &rb_output_rs, 0, rb_str_setter); rb_define_hooked_variable("$.", &lineno, 0, lineno_setter); rb_define_virtual_variable("$_", rb_lastline_get, rb_lastline_set); rb_define_method(rb_cIO, "initialize_copy", rb_io_init_copy, 1); rb_define_method(rb_cIO, "reopen", rb_io_reopen, -1); rb_define_method(rb_cIO, "print", rb_io_print, -1); rb_define_method(rb_cIO, "putc", rb_io_putc, 1); rb_define_method(rb_cIO, "puts", rb_io_puts, -1); rb_define_method(rb_cIO, "printf", rb_io_printf, -1); rb_define_method(rb_cIO, "each", rb_io_each_line, -1); rb_define_method(rb_cIO, "each_line", rb_io_each_line, -1); rb_define_method(rb_cIO, "each_byte", rb_io_each_byte, 0); rb_define_method(rb_cIO, "syswrite", rb_io_syswrite, 1); rb_define_method(rb_cIO, "sysread", rb_io_sysread, -1); rb_define_method(rb_cIO, "fileno", rb_io_fileno, 0); rb_define_alias(rb_cIO, "to_i", "fileno"); rb_define_method(rb_cIO, "to_io", rb_io_to_io, 0); rb_define_method(rb_cIO, "fsync", rb_io_fsync, 0); rb_define_method(rb_cIO, "sync", rb_io_sync, 0); rb_define_method(rb_cIO, "sync=", rb_io_set_sync, 1); rb_define_method(rb_cIO, "lineno", rb_io_lineno, 0); rb_define_method(rb_cIO, "lineno=", rb_io_set_lineno, 1); rb_define_method(rb_cIO, "readlines", rb_io_readlines, -1); rb_define_method(rb_cIO, "read_nonblock", io_read_nonblock, -1); rb_define_method(rb_cIO, "write_nonblock", rb_io_write_nonblock, 1); rb_define_method(rb_cIO, "readpartial", io_readpartial, -1); rb_define_method(rb_cIO, "read", io_read, -1); rb_define_method(rb_cIO, "write", io_write, 1); rb_define_method(rb_cIO, "gets", rb_io_gets_m, -1); rb_define_method(rb_cIO, "readline", rb_io_readline, -1); rb_define_method(rb_cIO, "getc", rb_io_getc, 0); rb_define_method(rb_cIO, "readchar", rb_io_readchar, 0); rb_define_method(rb_cIO, "ungetc",rb_io_ungetc, 1); rb_define_method(rb_cIO, "<<", rb_io_addstr, 1); rb_define_method(rb_cIO, "flush", rb_io_flush, 0); rb_define_method(rb_cIO, "tell", rb_io_tell, 0); rb_define_method(rb_cIO, "seek", rb_io_seek_m, -1); rb_define_const(rb_cIO, "SEEK_SET", INT2FIX(SEEK_SET)); rb_define_const(rb_cIO, "SEEK_CUR", INT2FIX(SEEK_CUR)); rb_define_const(rb_cIO, "SEEK_END", INT2FIX(SEEK_END)); rb_define_method(rb_cIO, "rewind", rb_io_rewind, 0); rb_define_method(rb_cIO, "pos", rb_io_tell, 0); rb_define_method(rb_cIO, "pos=", rb_io_set_pos, 1); rb_define_method(rb_cIO, "eof", rb_io_eof, 0); rb_define_method(rb_cIO, "eof?", rb_io_eof, 0); rb_define_method(rb_cIO, "close", rb_io_close_m, 0); rb_define_method(rb_cIO, "closed?", rb_io_closed, 0); rb_define_method(rb_cIO, "close_read", rb_io_close_read, 0); rb_define_method(rb_cIO, "close_write", rb_io_close_write, 0); rb_define_method(rb_cIO, "isatty", rb_io_isatty, 0); rb_define_method(rb_cIO, "tty?", rb_io_isatty, 0); rb_define_method(rb_cIO, "binmode", rb_io_binmode, 0); rb_define_method(rb_cIO, "sysseek", rb_io_sysseek, -1); rb_define_method(rb_cIO, "ioctl", rb_io_ioctl, -1); rb_define_method(rb_cIO, "fcntl", rb_io_fcntl, -1); rb_define_method(rb_cIO, "pid", rb_io_pid, 0); rb_define_method(rb_cIO, "inspect", rb_io_inspect, 0); rb_define_variable("$stdin", &rb_stdin); rb_stdin = prep_stdio(stdin, FMODE_READABLE, rb_cIO); rb_define_hooked_variable("$stdout", &rb_stdout, 0, stdout_setter); rb_stdout = prep_stdio(stdout, FMODE_WRITABLE, rb_cIO); rb_define_hooked_variable("$stderr", &rb_stderr, 0, stdout_setter); rb_stderr = prep_stdio(stderr, FMODE_WRITABLE, rb_cIO); rb_define_hooked_variable("$>", &rb_stdout, 0, stdout_setter); orig_stdout = rb_stdout; rb_deferr = orig_stderr = rb_stderr; /* variables to be removed in 1.8.1 */ rb_define_hooked_variable("$defout", &rb_stdout, 0, defout_setter); rb_define_hooked_variable("$deferr", &rb_stderr, 0, deferr_setter); /* constants to hold original stdin/stdout/stderr */ rb_define_global_const("STDIN", rb_stdin); rb_define_global_const("STDOUT", rb_stdout); rb_define_global_const("STDERR", rb_stderr); rb_define_readonly_variable("$<", &argf); argf = rb_obj_alloc(rb_cObject); rb_extend_object(argf, rb_mEnumerable); rb_define_global_const("ARGF", argf); rb_define_singleton_method(argf, "to_s", argf_to_s, 0); rb_define_singleton_method(argf, "fileno", argf_fileno, 0); rb_define_singleton_method(argf, "to_i", argf_fileno, 0); rb_define_singleton_method(argf, "to_io", argf_to_io, 0); rb_define_singleton_method(argf, "each", argf_each_line, -1); rb_define_singleton_method(argf, "each_line", argf_each_line, -1); rb_define_singleton_method(argf, "each_byte", argf_each_byte, 0); rb_define_singleton_method(argf, "read", argf_read, -1); rb_define_singleton_method(argf, "readlines", rb_f_readlines, -1); rb_define_singleton_method(argf, "to_a", rb_f_readlines, -1); rb_define_singleton_method(argf, "gets", rb_f_gets, -1); rb_define_singleton_method(argf, "readline", rb_f_readline, -1); rb_define_singleton_method(argf, "getc", argf_getc, 0); rb_define_singleton_method(argf, "readchar", argf_readchar, 0); rb_define_singleton_method(argf, "tell", argf_tell, 0); rb_define_singleton_method(argf, "seek", argf_seek_m, -1); rb_define_singleton_method(argf, "rewind", argf_rewind, 0); rb_define_singleton_method(argf, "pos", argf_tell, 0); rb_define_singleton_method(argf, "pos=", argf_set_pos, 1); rb_define_singleton_method(argf, "eof", argf_eof, 0); rb_define_singleton_method(argf, "eof?", argf_eof, 0); rb_define_singleton_method(argf, "binmode", argf_binmode, 0); rb_define_singleton_method(argf, "filename", argf_filename, 0); rb_define_singleton_method(argf, "path", argf_filename, 0); rb_define_singleton_method(argf, "file", argf_file, 0); rb_define_singleton_method(argf, "skip", argf_skip, 0); rb_define_singleton_method(argf, "close", argf_close_m, 0); rb_define_singleton_method(argf, "closed?", argf_closed, 0); rb_define_singleton_method(argf, "lineno", argf_lineno, 0); rb_define_singleton_method(argf, "lineno=", argf_set_lineno, 1); rb_global_variable(¤t_file); rb_define_readonly_variable("$FILENAME", &filename); filename = rb_str_new2("-"); rb_define_virtual_variable("$-i", opt_i_get, opt_i_set); #if defined (_WIN32) || defined(DJGPP) || defined(__CYGWIN__) || defined(__human68k__) atexit(pipe_atexit); #endif Init_File(); rb_define_method(rb_cFile, "initialize", rb_file_initialize, -1); rb_file_const("RDONLY", INT2FIX(O_RDONLY)); rb_file_const("WRONLY", INT2FIX(O_WRONLY)); rb_file_const("RDWR", INT2FIX(O_RDWR)); rb_file_const("APPEND", INT2FIX(O_APPEND)); rb_file_const("CREAT", INT2FIX(O_CREAT)); rb_file_const("EXCL", INT2FIX(O_EXCL)); #if defined(O_NDELAY) || defined(O_NONBLOCK) # ifdef O_NONBLOCK rb_file_const("NONBLOCK", INT2FIX(O_NONBLOCK)); # else rb_file_const("NONBLOCK", INT2FIX(O_NDELAY)); # endif #endif rb_file_const("TRUNC", INT2FIX(O_TRUNC)); #ifdef O_NOCTTY rb_file_const("NOCTTY", INT2FIX(O_NOCTTY)); #endif #ifdef O_BINARY rb_file_const("BINARY", INT2FIX(O_BINARY)); #endif #ifdef O_SYNC rb_file_const("SYNC", INT2FIX(O_SYNC)); #endif } /* C code produced by gperf version 2.7.2 */ /* Command-line: gperf -p -j1 -i 1 -g -o -t -N rb_reserved_word -k'1,3,$' ./keywords */ struct kwtable {char *name; int id[2]; enum lex_state state;}; #define TOTAL_KEYWORDS 40 #define MIN_WORD_LENGTH 2 #define MAX_WORD_LENGTH 8 #define MIN_HASH_VALUE 6 #define MAX_HASH_VALUE 55 /* maximum key range = 50, duplicates = 0 */ #ifdef __GNUC__ __inline #else #ifdef __cplusplus inline #endif #endif static unsigned int hash (str, len) register const char *str; register unsigned int len; { static unsigned char asso_values[] = { 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 11, 56, 56, 36, 56, 1, 37, 31, 1, 56, 56, 56, 56, 29, 56, 1, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 1, 56, 32, 1, 2, 1, 1, 4, 23, 56, 17, 56, 20, 9, 2, 9, 26, 14, 56, 5, 1, 1, 16, 56, 21, 20, 9, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56 }; register int hval = len; switch (hval) { default: case 3: hval += asso_values[(unsigned char)str[2]]; case 2: case 1: hval += asso_values[(unsigned char)str[0]]; break; } return hval + asso_values[(unsigned char)str[len - 1]]; } #ifdef __GNUC__ __inline #endif struct kwtable * rb_reserved_word (str, len) register const char *str; register unsigned int len; { static struct kwtable wordlist[] = { {""}, {""}, {""}, {""}, {""}, {""}, {"end", {kEND, kEND}, EXPR_END}, {"else", {kELSE, kELSE}, EXPR_BEG}, {"case", {kCASE, kCASE}, EXPR_BEG}, {"ensure", {kENSURE, kENSURE}, EXPR_BEG}, {"module", {kMODULE, kMODULE}, EXPR_BEG}, {"elsif", {kELSIF, kELSIF}, EXPR_BEG}, {"def", {kDEF, kDEF}, EXPR_FNAME}, {"rescue", {kRESCUE, kRESCUE_MOD}, EXPR_MID}, {"not", {kNOT, kNOT}, EXPR_BEG}, {"then", {kTHEN, kTHEN}, EXPR_BEG}, {"yield", {kYIELD, kYIELD}, EXPR_ARG}, {"for", {kFOR, kFOR}, EXPR_BEG}, {"self", {kSELF, kSELF}, EXPR_END}, {"false", {kFALSE, kFALSE}, EXPR_END}, {"retry", {kRETRY, kRETRY}, EXPR_END}, {"return", {kRETURN, kRETURN}, EXPR_MID}, {"true", {kTRUE, kTRUE}, EXPR_END}, {"if", {kIF, kIF_MOD}, EXPR_BEG}, {"defined?", {kDEFINED, kDEFINED}, EXPR_ARG}, {"super", {kSUPER, kSUPER}, EXPR_ARG}, {"undef", {kUNDEF, kUNDEF}, EXPR_FNAME}, {"break", {kBREAK, kBREAK}, EXPR_MID}, {"in", {kIN, kIN}, EXPR_BEG}, {"do", {kDO, kDO}, EXPR_BEG}, {"nil", {kNIL, kNIL}, EXPR_END}, {"until", {kUNTIL, kUNTIL_MOD}, EXPR_BEG}, {"unless", {kUNLESS, kUNLESS_MOD}, EXPR_BEG}, {"or", {kOR, kOR}, EXPR_BEG}, {"next", {kNEXT, kNEXT}, EXPR_MID}, {"when", {kWHEN, kWHEN}, EXPR_BEG}, {"redo", {kREDO, kREDO}, EXPR_END}, {"and", {kAND, kAND}, EXPR_BEG}, {"begin", {kBEGIN, kBEGIN}, EXPR_BEG}, {"__LINE__", {k__LINE__, k__LINE__}, EXPR_END}, {"class", {kCLASS, kCLASS}, EXPR_CLASS}, {"__FILE__", {k__FILE__, k__FILE__}, EXPR_END}, {"END", {klEND, klEND}, EXPR_END}, {"BEGIN", {klBEGIN, klBEGIN}, EXPR_END}, {"while", {kWHILE, kWHILE_MOD}, EXPR_BEG}, {""}, {""}, {""}, {""}, {""}, {""}, {""}, {""}, {""}, {""}, {"alias", {kALIAS, kALIAS}, EXPR_FNAME} }; if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) { register int key = hash (str, len); if (key <= MAX_HASH_VALUE && key >= 0) { register const char *s = wordlist[key].name; if (*str == *s && !strcmp (str + 1, s + 1)) return &wordlist[key]; } } return 0; } /********************************************************************** main.c - $Author: shyouhei $ $Date: 2007-02-13 00:01:19 +0100 (Tue, 13 Feb 2007) $ created at: Fri Aug 19 13:19:58 JST 1994 Copyright (C) 1993-2003 Yukihiro Matsumoto **********************************************************************/ #include "ruby.h" #ifdef __human68k__ int _stacksize = 262144; #endif #if defined __MINGW32__ int _CRT_glob = 0; #endif #if defined(__MACOS__) && defined(__MWERKS__) #include #endif /* to link startup code with ObjC support */ #if (defined(__APPLE__) || defined(__NeXT__)) && defined(__MACH__) static void objcdummyfunction( void ) { objc_msgSend(); } #endif int main(argc, argv, envp) int argc; char **argv, **envp; { #ifdef _WIN32 NtInitialize(&argc, &argv); #endif #if defined(__MACOS__) && defined(__MWERKS__) argc = ccommand(&argv); #endif { RUBY_INIT_STACK ruby_init(); ruby_options(argc, argv); ruby_run(); } return 0; } /********************************************************************** marshal.c - $Author: shyouhei $ $Date: 2009-01-28 12:59:58 +0100 (Wed, 28 Jan 2009) $ created at: Thu Apr 27 16:30:01 JST 1995 Copyright (C) 1993-2003 Yukihiro Matsumoto **********************************************************************/ #include "ruby.h" #include "rubyio.h" #include "st.h" #include "util.h" #include #ifdef HAVE_FLOAT_H #include #endif #ifdef HAVE_IEEEFP_H #include #endif #define BITSPERSHORT (2*CHAR_BIT) #define SHORTMASK ((1<wrapper)) { rb_raise(rb_eRuntimeError, "Marshal.dump reentered at %s", rb_id2name(sym)); } } static void mark_dump_arg(ptr) void *ptr; { struct dump_arg *p = ptr; if (!ptr) return; rb_mark_set(p->data); } static VALUE class2path(klass) VALUE klass; { VALUE path = rb_class_path(klass); char *n = RSTRING(path)->ptr; if (n[0] == '#') { rb_raise(rb_eTypeError, "can't dump anonymous %s %s", (TYPE(klass) == T_CLASS ? "class" : "module"), n); } if (rb_path2class(n) != rb_class_real(klass)) { rb_raise(rb_eTypeError, "%s can't be referred", n); } return path; } static void w_long _((long, struct dump_arg*)); static void w_nbyte(s, n, arg) char *s; int n; struct dump_arg *arg; { VALUE buf = arg->str; rb_str_buf_cat(buf, s, n); if (arg->dest && RSTRING(buf)->len >= BUFSIZ) { if (arg->taint) OBJ_TAINT(buf); rb_io_write(arg->dest, buf); rb_str_resize(buf, 0); } } static void w_byte(c, arg) char c; struct dump_arg *arg; { w_nbyte(&c, 1, arg); } static void w_bytes(s, n, arg) char *s; int n; struct dump_arg *arg; { w_long(n, arg); w_nbyte(s, n, arg); } static void w_short(x, arg) int x; struct dump_arg *arg; { w_byte((x >> 0) & 0xff, arg); w_byte((x >> 8) & 0xff, arg); } static void w_long(x, arg) long x; struct dump_arg *arg; { char buf[sizeof(long)+1]; int i, len = 0; #if SIZEOF_LONG > 4 if (!(RSHIFT(x, 31) == 0 || RSHIFT(x, 31) == -1)) { /* big long does not fit in 4 bytes */ rb_raise(rb_eTypeError, "long too big to dump"); } #endif if (x == 0) { w_byte(0, arg); return; } if (0 < x && x < 123) { w_byte(x + 5, arg); return; } if (-124 < x && x < 0) { w_byte((x - 5)&0xff, arg); return; } for (i=1;i 32 #define MANT_BITS 32 #elif DBL_MANT_DIG > 24 #define MANT_BITS 24 #elif DBL_MANT_DIG > 16 #define MANT_BITS 16 #else #define MANT_BITS 8 #endif static int save_mantissa(d, buf) double d; char *buf; { int e, i = 0; unsigned long m; double n; d = modf(ldexp(frexp(fabs(d), &e), DECIMAL_MANT), &d); if (d > 0) { buf[i++] = 0; do { d = modf(ldexp(d, MANT_BITS), &n); m = (unsigned long)n; #if MANT_BITS > 24 buf[i++] = m >> 24; #endif #if MANT_BITS > 16 buf[i++] = m >> 16; #endif #if MANT_BITS > 8 buf[i++] = m >> 8; #endif buf[i++] = m; } while (d > 0); while (!buf[i - 1]) --i; } return i; } static double load_mantissa(d, buf, len) double d; const char *buf; int len; { if (--len > 0 && !*buf++) { /* binary mantissa mark */ int e, s = d < 0, dig = 0; unsigned long m; modf(ldexp(frexp(fabs(d), &e), DECIMAL_MANT), &d); do { m = 0; switch (len) { default: m = *buf++ & 0xff; #if MANT_BITS > 24 case 3: m = (m << 8) | (*buf++ & 0xff); #endif #if MANT_BITS > 16 case 2: m = (m << 8) | (*buf++ & 0xff); #endif #if MANT_BITS > 8 case 1: m = (m << 8) | (*buf++ & 0xff); #endif } dig -= len < MANT_BITS / 8 ? 8 * (unsigned)len : MANT_BITS; d += ldexp((double)m, dig); } while ((len -= MANT_BITS / 8) > 0); d = ldexp(d, e - DECIMAL_MANT); if (s) d = -d; } return d; } #else #define load_mantissa(d, buf, len) (d) #define save_mantissa(d, buf) 0 #endif #ifdef DBL_DIG #define FLOAT_DIG (DBL_DIG+2) #else #define FLOAT_DIG 17 #endif static void w_float(d, arg) double d; struct dump_arg *arg; { char buf[100]; if (isinf(d)) { if (d < 0) strcpy(buf, "-inf"); else strcpy(buf, "inf"); } else if (isnan(d)) { strcpy(buf, "nan"); } else if (d == 0.0) { if (1.0/d < 0) strcpy(buf, "-0"); else strcpy(buf, "0"); } else { int len; /* xxx: should not use system's sprintf(3) */ sprintf(buf, "%.*g", FLOAT_DIG, d); len = strlen(buf); w_bytes(buf, len + save_mantissa(d, buf + len), arg); return; } w_bytes(buf, strlen(buf), arg); } static void w_symbol(id, arg) ID id; struct dump_arg *arg; { char *sym = rb_id2name(id); st_data_t num; if (st_lookup(arg->symbols, id, &num)) { w_byte(TYPE_SYMLINK, arg); w_long((long)num, arg); } else { w_byte(TYPE_SYMBOL, arg); w_bytes(sym, strlen(sym), arg); st_add_direct(arg->symbols, id, arg->symbols->num_entries); } } static void w_unique(s, arg) char *s; struct dump_arg *arg; { if (s[0] == '#') { rb_raise(rb_eTypeError, "can't dump anonymous class %s", s); } w_symbol(rb_intern(s), arg); } static void w_object _((VALUE,struct dump_arg*,int)); static int hash_each(key, value, arg) VALUE key, value; struct dump_call_arg *arg; { w_object(key, arg->arg, arg->limit); w_object(value, arg->arg, arg->limit); return ST_CONTINUE; } static void w_extended(klass, arg, check) VALUE klass; struct dump_arg *arg; int check; { char *path; if (check && FL_TEST(klass, FL_SINGLETON)) { if (RCLASS(klass)->m_tbl->num_entries || (RCLASS(klass)->iv_tbl && RCLASS(klass)->iv_tbl->num_entries > 1)) { rb_raise(rb_eTypeError, "singleton can't be dumped"); } klass = RCLASS(klass)->super; } while (BUILTIN_TYPE(klass) == T_ICLASS) { path = rb_class2name(RBASIC(klass)->klass); w_byte(TYPE_EXTENDED, arg); w_unique(path, arg); klass = RCLASS(klass)->super; } } static void w_class(type, obj, arg, check) int type; VALUE obj; struct dump_arg *arg; int check; { char *path; VALUE klass = CLASS_OF(obj); w_extended(klass, arg, check); w_byte(type, arg); path = RSTRING(class2path(rb_class_real(klass)))->ptr; w_unique(path, arg); } static void w_uclass(obj, base_klass, arg) VALUE obj, base_klass; struct dump_arg *arg; { VALUE klass = CLASS_OF(obj); w_extended(klass, arg, Qtrue); klass = rb_class_real(klass); if (klass != base_klass) { w_byte(TYPE_UCLASS, arg); w_unique(RSTRING(class2path(klass))->ptr, arg); } } static int w_obj_each(id, value, arg) ID id; VALUE value; struct dump_call_arg *arg; { w_symbol(id, arg->arg); w_object(value, arg->arg, arg->limit); return ST_CONTINUE; } static void w_ivar(tbl, arg) st_table *tbl; struct dump_call_arg *arg; { if (tbl) { w_long(tbl->num_entries, arg->arg); st_foreach_safe(tbl, w_obj_each, (st_data_t)arg); } else { w_long(0, arg->arg); } } static void w_object(obj, arg, limit) VALUE obj; struct dump_arg *arg; int limit; { struct dump_call_arg c_arg; st_table *ivtbl = 0; st_data_t num; if (limit == 0) { rb_raise(rb_eArgError, "exceed depth limit"); } limit--; c_arg.limit = limit; c_arg.arg = arg; if (st_lookup(arg->data, obj, &num)) { w_byte(TYPE_LINK, arg); w_long((long)num, arg); return; } if ((ivtbl = rb_generic_ivar_table(obj)) != 0) { w_byte(TYPE_IVAR, arg); } if (obj == Qnil) { w_byte(TYPE_NIL, arg); } else if (obj == Qtrue) { w_byte(TYPE_TRUE, arg); } else if (obj == Qfalse) { w_byte(TYPE_FALSE, arg); } else if (FIXNUM_P(obj)) { #if SIZEOF_LONG <= 4 w_byte(TYPE_FIXNUM, arg); w_long(FIX2INT(obj), arg); #else if (RSHIFT((long)obj, 31) == 0 || RSHIFT((long)obj, 31) == -1) { w_byte(TYPE_FIXNUM, arg); w_long(FIX2LONG(obj), arg); } else { w_object(rb_int2big(FIX2LONG(obj)), arg, limit); } #endif } else if (SYMBOL_P(obj)) { w_symbol(SYM2ID(obj), arg); } else { if (OBJ_TAINTED(obj)) arg->taint = Qtrue; st_add_direct(arg->data, obj, arg->data->num_entries); if (rb_respond_to(obj, s_mdump)) { volatile VALUE v; v = rb_funcall(obj, s_mdump, 0, 0); check_dump_arg(arg, s_mdump); w_class(TYPE_USRMARSHAL, obj, arg, Qfalse); w_object(v, arg, limit); if (ivtbl) w_ivar(0, &c_arg); return; } if (rb_respond_to(obj, s_dump)) { VALUE v; v = rb_funcall(obj, s_dump, 1, INT2NUM(limit)); check_dump_arg(arg, s_dump); if (TYPE(v) != T_STRING) { rb_raise(rb_eTypeError, "_dump() must return string"); } if (!ivtbl && (ivtbl = rb_generic_ivar_table(v))) { w_byte(TYPE_IVAR, arg); } w_class(TYPE_USERDEF, obj, arg, Qfalse); w_bytes(RSTRING(v)->ptr, RSTRING(v)->len, arg); if (ivtbl) { w_ivar(ivtbl, &c_arg); } return; } switch (BUILTIN_TYPE(obj)) { case T_CLASS: if (FL_TEST(obj, FL_SINGLETON)) { rb_raise(rb_eTypeError, "singleton class can't be dumped"); } w_byte(TYPE_CLASS, arg); { VALUE path = class2path(obj); w_bytes(RSTRING(path)->ptr, RSTRING(path)->len, arg); } break; case T_MODULE: w_byte(TYPE_MODULE, arg); { VALUE path = class2path(obj); w_bytes(RSTRING(path)->ptr, RSTRING(path)->len, arg); } break; case T_FLOAT: w_byte(TYPE_FLOAT, arg); w_float(RFLOAT(obj)->value, arg); break; case T_BIGNUM: w_byte(TYPE_BIGNUM, arg); { char sign = RBIGNUM(obj)->sign ? '+' : '-'; long len = RBIGNUM(obj)->len; BDIGIT *d = RBIGNUM(obj)->digits; w_byte(sign, arg); w_long(SHORTLEN(len), arg); /* w_short? */ while (len--) { #if SIZEOF_BDIGITS > SIZEOF_SHORT BDIGIT num = *d; int i; for (i=0; iptr, RSTRING(obj)->len, arg); break; case T_REGEXP: w_uclass(obj, rb_cRegexp, arg); w_byte(TYPE_REGEXP, arg); w_bytes(RREGEXP(obj)->str, RREGEXP(obj)->len, arg); w_byte(rb_reg_options(obj), arg); break; case T_ARRAY: w_uclass(obj, rb_cArray, arg); w_byte(TYPE_ARRAY, arg); { long len = RARRAY(obj)->len; VALUE *ptr = RARRAY(obj)->ptr; w_long(len, arg); while (len--) { w_object(*ptr, arg, limit); ptr++; } } break; case T_HASH: w_uclass(obj, rb_cHash, arg); if (NIL_P(RHASH(obj)->ifnone)) { w_byte(TYPE_HASH, arg); } else if (FL_TEST(obj, FL_USER2)) { /* FL_USER2 means HASH_PROC_DEFAULT (see hash.c) */ rb_raise(rb_eTypeError, "can't dump hash with default proc"); } else { w_byte(TYPE_HASH_DEF, arg); } w_long(RHASH(obj)->tbl->num_entries, arg); rb_hash_foreach(obj, hash_each, (st_data_t)&c_arg); if (!NIL_P(RHASH(obj)->ifnone)) { w_object(RHASH(obj)->ifnone, arg, limit); } break; case T_STRUCT: w_class(TYPE_STRUCT, obj, arg, Qtrue); { long len = RSTRUCT(obj)->len; VALUE mem; long i; w_long(len, arg); mem = rb_struct_members(obj); for (i=0; iptr[i]), arg); w_object(RSTRUCT(obj)->ptr[i], arg, limit); } } break; case T_OBJECT: w_class(TYPE_OBJECT, obj, arg, Qtrue); w_ivar(ROBJECT(obj)->iv_tbl, &c_arg); break; case T_DATA: { VALUE v; if (!rb_respond_to(obj, s_dump_data)) { rb_raise(rb_eTypeError, "no marshal_dump is defined for class %s", rb_obj_classname(obj)); } v = rb_funcall(obj, s_dump_data, 0); check_dump_arg(arg, s_dump_data); w_class(TYPE_DATA, obj, arg, Qtrue); w_object(v, arg, limit); } break; default: rb_raise(rb_eTypeError, "can't dump %s", rb_obj_classname(obj)); break; } } if (ivtbl) { w_ivar(ivtbl, &c_arg); } } static VALUE dump(arg) struct dump_call_arg *arg; { w_object(arg->obj, arg->arg, arg->limit); if (arg->arg->dest) { rb_io_write(arg->arg->dest, arg->arg->str); rb_str_resize(arg->arg->str, 0); } return 0; } static VALUE dump_ensure(arg) struct dump_arg *arg; { if (!DATA_PTR(arg->wrapper)) return 0; st_free_table(arg->symbols); st_free_table(arg->data); DATA_PTR(arg->wrapper) = 0; arg->wrapper = 0; if (arg->taint) { OBJ_TAINT(arg->str); } return 0; } /* * call-seq: * dump( obj [, anIO] , limit=--1 ) => anIO * * Serializes obj and all descendent objects. If anIO is * specified, the serialized data will be written to it, otherwise the * data will be returned as a String. If limit is specified, the * traversal of subobjects will be limited to that depth. If limit is * negative, no checking of depth will be performed. * * class Klass * def initialize(str) * @str = str * end * def sayHello * @str * end * end * * (produces no output) * * o = Klass.new("hello\n") * data = Marshal.dump(o) * obj = Marshal.load(data) * obj.sayHello #=> "hello\n" */ static VALUE marshal_dump(argc, argv) int argc; VALUE* argv; { VALUE obj, port, a1, a2; int limit = -1; struct dump_arg arg; struct dump_call_arg c_arg; port = Qnil; rb_scan_args(argc, argv, "12", &obj, &a1, &a2); if (argc == 3) { if (!NIL_P(a2)) limit = NUM2INT(a2); if (NIL_P(a1)) goto type_error; port = a1; } else if (argc == 2) { if (FIXNUM_P(a1)) limit = FIX2INT(a1); else if (NIL_P(a1)) goto type_error; else port = a1; } arg.dest = 0; arg.symbols = st_init_numtable(); arg.data = st_init_numtable(); arg.taint = Qfalse; arg.str = rb_str_buf_new(0); RBASIC(arg.str)->klass = 0; arg.wrapper = Data_Wrap_Struct(rb_cData, mark_dump_arg, 0, &arg); if (!NIL_P(port)) { if (!rb_respond_to(port, s_write)) { type_error: rb_raise(rb_eTypeError, "instance of IO needed"); } arg.dest = port; if (rb_respond_to(port, s_binmode)) { rb_funcall2(port, s_binmode, 0, 0); check_dump_arg(&arg, s_binmode); } } else { port = arg.str; } c_arg.obj = obj; c_arg.arg = &arg; c_arg.limit = limit; w_byte(MARSHAL_MAJOR, &arg); w_byte(MARSHAL_MINOR, &arg); rb_ensure(dump, (VALUE)&c_arg, dump_ensure, (VALUE)&arg); RBASIC(arg.str)->klass = rb_cString; return port; } struct load_arg { VALUE src; long offset; st_table *symbols; st_table *data; VALUE proc; int taint; VALUE wrapper; }; static void check_load_arg(arg, sym) struct load_arg *arg; ID sym; { if (!DATA_PTR(arg->wrapper)) { rb_raise(rb_eRuntimeError, "Marshal.load reentered at %s", rb_id2name(sym)); } } static void mark_load_arg(ptr) void *ptr; { struct load_arg *p = ptr; if (!ptr) return; rb_mark_tbl(p->data); } static VALUE r_object _((struct load_arg *arg)); static int r_byte(arg) struct load_arg *arg; { int c; if (TYPE(arg->src) == T_STRING) { if (RSTRING(arg->src)->len > arg->offset) { c = (unsigned char)RSTRING(arg->src)->ptr[arg->offset++]; } else { rb_raise(rb_eArgError, "marshal data too short"); } } else { VALUE src = arg->src; VALUE v = rb_funcall2(src, s_getc, 0, 0); check_load_arg(arg, s_getc); if (NIL_P(v)) rb_eof_error(); c = (unsigned char)FIX2INT(v); } return c; } static void long_toobig(size) int size; { rb_raise(rb_eTypeError, "long too big for this architecture (size %d, given %d)", sizeof(long), size); } #undef SIGN_EXTEND_CHAR #if __STDC__ # define SIGN_EXTEND_CHAR(c) ((signed char)(c)) #else /* not __STDC__ */ /* As in Harbison and Steele. */ # define SIGN_EXTEND_CHAR(c) ((((unsigned char)(c)) ^ 128) - 128) #endif static long r_long(arg) struct load_arg *arg; { register long x; int c = SIGN_EXTEND_CHAR(r_byte(arg)); long i; if (c == 0) return 0; if (c > 0) { if (4 < c && c < 128) { return c - 5; } if (c > sizeof(long)) long_toobig(c); x = 0; for (i=0;i sizeof(long)) long_toobig(c); x = -1; for (i=0;isrc) == T_STRING) { if (RSTRING(arg->src)->len - arg->offset >= len) { str = rb_str_new(RSTRING(arg->src)->ptr+arg->offset, len); arg->offset += len; } else { too_short: rb_raise(rb_eArgError, "marshal data too short"); } } else { VALUE src = arg->src; VALUE n = LONG2NUM(len); str = rb_funcall2(src, s_read, 1, &n); check_load_arg(arg, s_read); if (NIL_P(str)) goto too_short; StringValue(str); if (RSTRING(str)->len != len) goto too_short; if (OBJ_TAINTED(str)) arg->taint = Qtrue; } return str; } static ID r_symlink(arg) struct load_arg *arg; { ID id; long num = r_long(arg); if (st_lookup(arg->symbols, num, &id)) { return id; } rb_raise(rb_eArgError, "bad symbol"); } static ID r_symreal(arg) struct load_arg *arg; { ID id; id = rb_intern(RSTRING(r_bytes(arg))->ptr); st_insert(arg->symbols, arg->symbols->num_entries, id); return id; } static ID r_symbol(arg) struct load_arg *arg; { if (r_byte(arg) == TYPE_SYMLINK) { return r_symlink(arg); } return r_symreal(arg); } static char* r_unique(arg) struct load_arg *arg; { return rb_id2name(r_symbol(arg)); } static VALUE r_string(arg) struct load_arg *arg; { return r_bytes(arg); } static VALUE r_entry(v, arg) VALUE v; struct load_arg *arg; { st_insert(arg->data, arg->data->num_entries, (st_data_t)v); if (arg->taint) OBJ_TAINT(v); return v; } static void r_ivar(obj, arg) VALUE obj; struct load_arg *arg; { long len; len = r_long(arg); if (len > 0) { while (len--) { ID id = r_symbol(arg); VALUE val = r_object(arg); rb_ivar_set(obj, id, val); } } } static VALUE path2class(path) char *path; { VALUE v = rb_path2class(path); if (TYPE(v) != T_CLASS) { rb_raise(rb_eArgError, "%s does not refer class", path); } return v; } static VALUE path2module(path) char *path; { VALUE v = rb_path2class(path); if (TYPE(v) != T_MODULE) { rb_raise(rb_eArgError, "%s does not refer module", path); } return v; } static VALUE r_object0(arg, proc, ivp, extmod) struct load_arg *arg; VALUE proc; int *ivp; VALUE extmod; { VALUE v = Qnil; int type = r_byte(arg); long id; st_data_t link; switch (type) { case TYPE_LINK: id = r_long(arg); if (!st_lookup(arg->data, (st_data_t)id, &link)) { rb_raise(rb_eArgError, "dump format error (unlinked)"); } v = (st_data_t)link; return v; case TYPE_IVAR: { int ivar = Qtrue; v = r_object0(arg, 0, &ivar, extmod); if (ivar) r_ivar(v, arg); } break; case TYPE_EXTENDED: { VALUE m = path2module(r_unique(arg)); if (NIL_P(extmod)) extmod = rb_ary_new2(0); rb_ary_push(extmod, m); v = r_object0(arg, 0, 0, extmod); while (RARRAY(extmod)->len > 0) { m = rb_ary_pop(extmod); rb_extend_object(v, m); } } break; case TYPE_UCLASS: { VALUE c = path2class(r_unique(arg)); if (FL_TEST(c, FL_SINGLETON)) { rb_raise(rb_eTypeError, "singleton can't be loaded"); } v = r_object0(arg, 0, 0, extmod); if (rb_special_const_p(v) || TYPE(v) == T_OBJECT || TYPE(v) == T_CLASS) { format_error: rb_raise(rb_eArgError, "dump format error (user class)"); } if (TYPE(v) == T_MODULE || !RTEST(rb_class_inherited_p(c, RBASIC(v)->klass))) { VALUE tmp = rb_obj_alloc(c); if (TYPE(v) != TYPE(tmp)) goto format_error; } RBASIC(v)->klass = c; } break; case TYPE_NIL: v = Qnil; break; case TYPE_TRUE: v = Qtrue; break; case TYPE_FALSE: v = Qfalse; break; case TYPE_FIXNUM: { long i = r_long(arg); v = LONG2FIX(i); } break; case TYPE_FLOAT: { double d, t = 0.0; VALUE str = r_bytes(arg); const char *ptr = RSTRING(str)->ptr; if (strcmp(ptr, "nan") == 0) { d = t / t; } else if (strcmp(ptr, "inf") == 0) { d = 1.0 / t; } else if (strcmp(ptr, "-inf") == 0) { d = -1.0 / t; } else { char *e; d = strtod(ptr, &e); d = load_mantissa(d, e, RSTRING(str)->len - (e - ptr)); } v = rb_float_new(d); r_entry(v, arg); } break; case TYPE_BIGNUM: { long len; BDIGIT *digits; volatile VALUE data; NEWOBJ(big, struct RBignum); OBJSETUP(big, rb_cBignum, T_BIGNUM); big->sign = (r_byte(arg) == '+'); len = r_long(arg); data = r_bytes0(len * 2, arg); #if SIZEOF_BDIGITS == SIZEOF_SHORT big->len = len; #else big->len = (len + 1) * 2 / sizeof(BDIGIT); #endif big->digits = digits = ALLOC_N(BDIGIT, big->len); MEMCPY(digits, RSTRING(data)->ptr, char, len * 2); #if SIZEOF_BDIGITS > SIZEOF_SHORT MEMZERO((char *)digits + len * 2, char, big->len * sizeof(BDIGIT) - len * 2); #endif len = big->len; while (len > 0) { unsigned char *p = (unsigned char *)digits; BDIGIT num = 0; #if SIZEOF_BDIGITS > SIZEOF_SHORT int shift = 0; int i; for (i=0; iptr, RSTRING(str)->len, options), arg); } break; case TYPE_ARRAY: { volatile long len = r_long(arg); /* gcc 2.7.2.3 -O2 bug?? */ v = rb_ary_new2(len); r_entry(v, arg); while (len--) { rb_ary_push(v, r_object(arg)); } } break; case TYPE_HASH: case TYPE_HASH_DEF: { long len = r_long(arg); v = rb_hash_new(); r_entry(v, arg); while (len--) { VALUE key = r_object(arg); VALUE value = r_object(arg); rb_hash_aset(v, key, value); } if (type == TYPE_HASH_DEF) { RHASH(v)->ifnone = r_object(arg); } } break; case TYPE_STRUCT: { VALUE klass, mem, values; volatile long i; /* gcc 2.7.2.3 -O2 bug?? */ long len; ID slot; klass = path2class(r_unique(arg)); mem = rb_struct_s_members(klass); if (mem == Qnil) { rb_raise(rb_eTypeError, "uninitialized struct"); } len = r_long(arg); values = rb_ary_new2(len); for (i=0; iptr[i] != ID2SYM(slot)) { rb_raise(rb_eTypeError, "struct %s not compatible (:%s for :%s)", rb_class2name(klass), rb_id2name(slot), rb_id2name(SYM2ID(RARRAY(mem)->ptr[i]))); } rb_struct_aset(v, LONG2FIX(i), r_object(arg)); } } break; case TYPE_USERDEF: { VALUE klass = path2class(r_unique(arg)); VALUE data; if (!rb_respond_to(klass, s_load)) { rb_raise(rb_eTypeError, "class %s needs to have method `_load'", rb_class2name(klass)); } data = r_string(arg); if (ivp) { r_ivar(data, arg); *ivp = Qfalse; } v = rb_funcall(klass, s_load, 1, data); check_load_arg(arg, s_load); r_entry(v, arg); } break; case TYPE_USRMARSHAL: { VALUE klass = path2class(r_unique(arg)); VALUE data; v = rb_obj_alloc(klass); if (! NIL_P(extmod)) { while (RARRAY(extmod)->len > 0) { VALUE m = rb_ary_pop(extmod); rb_extend_object(v, m); } } if (!rb_respond_to(v, s_mload)) { rb_raise(rb_eTypeError, "instance of %s needs to have method `marshal_load'", rb_class2name(klass)); } r_entry(v, arg); data = r_object(arg); rb_funcall(v, s_mload, 1, data); check_load_arg(arg, s_mload); } break; case TYPE_OBJECT: { VALUE klass = path2class(r_unique(arg)); v = rb_obj_alloc(klass); if (TYPE(v) != T_OBJECT) { rb_raise(rb_eArgError, "dump format error"); } r_entry(v, arg); r_ivar(v, arg); } break; case TYPE_DATA: { VALUE klass = path2class(r_unique(arg)); if (rb_respond_to(klass, s_alloc)) { static int warn = Qtrue; if (warn) { rb_warn("define `allocate' instead of `_alloc'"); warn = Qfalse; } v = rb_funcall(klass, s_alloc, 0); check_load_arg(arg, s_alloc); } else { v = rb_obj_alloc(klass); } if (TYPE(v) != T_DATA) { rb_raise(rb_eArgError, "dump format error"); } r_entry(v, arg); if (!rb_respond_to(v, s_load_data)) { rb_raise(rb_eTypeError, "class %s needs to have instance method `_load_data'", rb_class2name(klass)); } rb_funcall(v, s_load_data, 1, r_object0(arg, 0, 0, extmod)); check_load_arg(arg, s_load_data); } break; case TYPE_MODULE_OLD: { volatile VALUE str = r_bytes(arg); v = rb_path2class(RSTRING(str)->ptr); r_entry(v, arg); } break; case TYPE_CLASS: { volatile VALUE str = r_bytes(arg); v = path2class(RSTRING(str)->ptr); r_entry(v, arg); } break; case TYPE_MODULE: { volatile VALUE str = r_bytes(arg); v = path2module(RSTRING(str)->ptr); r_entry(v, arg); } break; case TYPE_SYMBOL: v = ID2SYM(r_symreal(arg)); break; case TYPE_SYMLINK: return ID2SYM(r_symlink(arg)); default: rb_raise(rb_eArgError, "dump format error(0x%x)", type); break; } if (proc) { rb_funcall(proc, s_call, 1, v); check_load_arg(arg, s_call); } return v; } static VALUE r_object(arg) struct load_arg *arg; { return r_object0(arg, arg->proc, 0, Qnil); } static VALUE load(arg) struct load_arg *arg; { return r_object(arg); } static VALUE load_ensure(arg) struct load_arg *arg; { if (!DATA_PTR(arg->wrapper)) return 0; st_free_table(arg->symbols); st_free_table(arg->data); DATA_PTR(arg->wrapper) = 0; arg->wrapper = 0; return 0; } /* * call-seq: * load( source [, proc] ) => obj * restore( source [, proc] ) => obj * * Returns the result of converting the serialized data in source into a * Ruby object (possibly with associated subordinate objects). source * may be either an instance of IO or an object that responds to * to_str. If proc is specified, it will be passed each object as it * is deserialized. */ static VALUE marshal_load(argc, argv) int argc; VALUE *argv; { VALUE port, proc; int major, minor; VALUE v; struct load_arg arg; rb_scan_args(argc, argv, "11", &port, &proc); v = rb_check_string_type(port); if (!NIL_P(v)) { arg.taint = OBJ_TAINTED(port); /* original taintedness */ port = v; } else if (rb_respond_to(port, s_getc) && rb_respond_to(port, s_read)) { if (rb_respond_to(port, s_binmode)) { rb_funcall2(port, s_binmode, 0, 0); } arg.taint = Qtrue; } else { rb_raise(rb_eTypeError, "instance of IO needed"); } arg.src = port; arg.offset = 0; arg.symbols = st_init_numtable(); arg.data = st_init_numtable(); arg.proc = 0; arg.wrapper = Data_Wrap_Struct(rb_cData, mark_load_arg, 0, &arg); major = r_byte(&arg); minor = r_byte(&arg); if (major != MARSHAL_MAJOR || minor > MARSHAL_MINOR) { rb_raise(rb_eTypeError, "incompatible marshal file format (can't be read)\n\ \tformat version %d.%d required; %d.%d given", MARSHAL_MAJOR, MARSHAL_MINOR, major, minor); } if (RTEST(ruby_verbose) && minor != MARSHAL_MINOR) { rb_warn("incompatible marshal file format (can be read)\n\ \tformat version %d.%d required; %d.%d given", MARSHAL_MAJOR, MARSHAL_MINOR, major, minor); } if (!NIL_P(proc)) arg.proc = proc; v = rb_ensure(load, (VALUE)&arg, load_ensure, (VALUE)&arg); return v; } /* * The marshaling library converts collections of Ruby objects into a * byte stream, allowing them to be stored outside the currently * active script. This data may subsequently be read and the original * objects reconstituted. * Marshaled data has major and minor version numbers stored along * with the object information. In normal use, marshaling can only * load data written with the same major version number and an equal * or lower minor version number. If Ruby's ``verbose'' flag is set * (normally using -d, -v, -w, or --verbose) the major and minor * numbers must match exactly. Marshal versioning is independent of * Ruby's version numbers. You can extract the version by reading the * first two bytes of marshaled data. * * str = Marshal.dump("thing") * RUBY_VERSION #=> "1.8.0" * str[0] #=> 4 * str[1] #=> 8 * * Some objects cannot be dumped: if the objects to be dumped include * bindings, procedure or method objects, instances of class IO, or * singleton objects, a TypeError will be raised. * If your class has special serialization needs (for example, if you * want to serialize in some specific format), or if it contains * objects that would otherwise not be serializable, you can implement * your own serialization strategy by defining two methods, _dump and * _load: * The instance method _dump should return a String object containing * all the information necessary to reconstitute objects of this class * and all referenced objects up to a maximum depth given as an integer * parameter (a value of -1 implies that you should disable depth checking). * The class method _load should take a String and return an object of this class. */ void Init_marshal() { VALUE rb_mMarshal = rb_define_module("Marshal"); s_dump = rb_intern("_dump"); s_load = rb_intern("_load"); s_mdump = rb_intern("marshal_dump"); s_mload = rb_intern("marshal_load"); s_dump_data = rb_intern("_dump_data"); s_load_data = rb_intern("_load_data"); s_alloc = rb_intern("_alloc"); s_call = rb_intern("call"); s_getc = rb_intern("getc"); s_read = rb_intern("read"); s_write = rb_intern("write"); s_binmode = rb_intern("binmode"); rb_define_module_function(rb_mMarshal, "dump", marshal_dump, -1); rb_define_module_function(rb_mMarshal, "load", marshal_load, -1); rb_define_module_function(rb_mMarshal, "restore", marshal_load, -1); rb_define_const(rb_mMarshal, "MAJOR_VERSION", INT2FIX(MARSHAL_MAJOR)); rb_define_const(rb_mMarshal, "MINOR_VERSION", INT2FIX(MARSHAL_MINOR)); } VALUE rb_marshal_dump(obj, port) VALUE obj, port; { int argc = 1; VALUE argv[2]; argv[0] = obj; argv[1] = port; if (!NIL_P(port)) argc = 2; return marshal_dump(argc, argv); } VALUE rb_marshal_load(port) VALUE port; { return marshal_load(1, &port); } /********************************************************************** math.c - $Author: shyouhei $ $Date: 2008-07-02 11:25:48 +0200 (Wed, 02 Jul 2008) $ created at: Tue Jan 25 14:12:56 JST 1994 Copyright (C) 1993-2003 Yukihiro Matsumoto **********************************************************************/ #include "ruby.h" #include #include VALUE rb_mMath; #define Need_Float(x) (x) = rb_Float(x) #define Need_Float2(x,y) do {\ Need_Float(x);\ Need_Float(y);\ } while (0) static void domain_check(x, msg) double x; char *msg; { while(1) { if (errno) { rb_sys_fail(msg); } if (isnan(x)) { #if defined(EDOM) errno = EDOM; #elif defined(ERANGE) errno = ERANGE; #endif continue; } break; } } /* * call-seq: * Math.atan2(y, x) => float * * Computes the arc tangent given y and x. Returns * -PI..PI. * */ static VALUE math_atan2(obj, y, x) VALUE obj, x, y; { Need_Float2(y, x); return rb_float_new(atan2(RFLOAT(y)->value, RFLOAT(x)->value)); } /* * call-seq: * Math.cos(x) => float * * Computes the cosine of x (expressed in radians). Returns * -1..1. */ static VALUE math_cos(obj, x) VALUE obj, x; { Need_Float(x); return rb_float_new(cos(RFLOAT(x)->value)); } /* * call-seq: * Math.sin(x) => float * * Computes the sine of x (expressed in radians). Returns * -1..1. */ static VALUE math_sin(obj, x) VALUE obj, x; { Need_Float(x); return rb_float_new(sin(RFLOAT(x)->value)); } /* * call-seq: * Math.tan(x) => float * * Returns the tangent of x (expressed in radians). */ static VALUE math_tan(obj, x) VALUE obj, x; { Need_Float(x); return rb_float_new(tan(RFLOAT(x)->value)); } /* * call-seq: * Math.acos(x) => float * * Computes the arc cosine of x. Returns 0..PI. */ static VALUE math_acos(obj, x) VALUE obj, x; { double d; Need_Float(x); errno = 0; d = acos(RFLOAT(x)->value); domain_check(d, "acos"); return rb_float_new(d); } /* * call-seq: * Math.asin(x) => float * * Computes the arc sine of x. Returns 0..PI. */ static VALUE math_asin(obj, x) VALUE obj, x; { double d; Need_Float(x); errno = 0; d = asin(RFLOAT(x)->value); domain_check(d, "asin"); return rb_float_new(d); } /* * call-seq: * Math.atan(x) => float * * Computes the arc tangent of x. Returns -{PI/2} .. {PI/2}. */ static VALUE math_atan(obj, x) VALUE obj, x; { Need_Float(x); return rb_float_new(atan(RFLOAT(x)->value)); } #ifndef HAVE_COSH double cosh(x) double x; { return (exp(x) + exp(-x)) / 2; } #endif /* * call-seq: * Math.cosh(x) => float * * Computes the hyperbolic cosine of x (expressed in radians). */ static VALUE math_cosh(obj, x) VALUE obj, x; { Need_Float(x); return rb_float_new(cosh(RFLOAT(x)->value)); } #ifndef HAVE_SINH double sinh(x) double x; { return (exp(x) - exp(-x)) / 2; } #endif /* * call-seq: * Math.sinh(x) => float * * Computes the hyperbolic sine of x (expressed in * radians). */ static VALUE math_sinh(obj, x) VALUE obj, x; { Need_Float(x); return rb_float_new(sinh(RFLOAT(x)->value)); } #ifndef HAVE_TANH double tanh(x) double x; { return sinh(x) / cosh(x); } #endif /* * call-seq: * Math.tanh() => float * * Computes the hyperbolic tangent of x (expressed in * radians). */ static VALUE math_tanh(obj, x) VALUE obj, x; { Need_Float(x); return rb_float_new(tanh(RFLOAT(x)->value)); } /* * call-seq: * Math.acosh(x) => float * * Computes the inverse hyperbolic cosine of x. */ static VALUE math_acosh(obj, x) VALUE obj, x; { double d; Need_Float(x); errno = 0; d = acosh(RFLOAT(x)->value); domain_check(d, "acosh"); return rb_float_new(d); } /* * call-seq: * Math.asinh(x) => float * * Computes the inverse hyperbolic sine of x. */ static VALUE math_asinh(obj, x) VALUE obj, x; { Need_Float(x); return rb_float_new(asinh(RFLOAT(x)->value)); } /* * call-seq: * Math.atanh(x) => float * * Computes the inverse hyperbolic tangent of x. */ static VALUE math_atanh(obj, x) VALUE obj, x; { double d; Need_Float(x); errno = 0; d = atanh(RFLOAT(x)->value); domain_check(d, "atanh"); return rb_float_new(d); } /* * call-seq: * Math.exp(x) => float * * Returns e**x. */ static VALUE math_exp(obj, x) VALUE obj, x; { Need_Float(x); return rb_float_new(exp(RFLOAT(x)->value)); } #if defined __CYGWIN__ # include # if CYGWIN_VERSION_DLL_MAJOR < 1005 # define nan(x) nan() # endif # define log(x) ((x) < 0.0 ? nan("") : log(x)) # define log10(x) ((x) < 0.0 ? nan("") : log10(x)) #endif /* * call-seq: * Math.log(numeric) => float * * Returns the natural logarithm of numeric. */ static VALUE math_log(obj, x) VALUE obj, x; { double d; Need_Float(x); errno = 0; d = log(RFLOAT(x)->value); domain_check(d, "log"); return rb_float_new(d); } /* * call-seq: * Math.log10(numeric) => float * * Returns the base 10 logarithm of numeric. */ static VALUE math_log10(obj, x) VALUE obj, x; { double d; Need_Float(x); errno = 0; d = log10(RFLOAT(x)->value); domain_check(d, "log10"); return rb_float_new(d); } /* * call-seq: * Math.sqrt(numeric) => float * * Returns the non-negative square root of numeric. */ static VALUE math_sqrt(obj, x) VALUE obj, x; { double d; Need_Float(x); errno = 0; d = sqrt(RFLOAT(x)->value); domain_check(d, "sqrt"); return rb_float_new(d); } /* * call-seq: * Math.frexp(numeric) => [ fraction, exponent ] * * Returns a two-element array containing the normalized fraction (a * Float) and exponent (a Fixnum) of * numeric. * * fraction, exponent = Math.frexp(1234) #=> [0.6025390625, 11] * fraction * 2**exponent #=> 1234.0 */ static VALUE math_frexp(obj, x) VALUE obj, x; { double d; int exp; Need_Float(x); d = frexp(RFLOAT(x)->value, &exp); return rb_assoc_new(rb_float_new(d), INT2NUM(exp)); } /* * call-seq: * Math.ldexp(flt, int) -> float * * Returns the value of flt*(2**int). * * fraction, exponent = Math.frexp(1234) * Math.ldexp(fraction, exponent) #=> 1234.0 */ static VALUE math_ldexp(obj, x, n) VALUE obj, x, n; { Need_Float(x); return rb_float_new(ldexp(RFLOAT(x)->value, NUM2INT(n))); } /* * call-seq: * Math.hypot(x, y) => float * * Returns sqrt(x**2 + y**2), the hypotenuse of a right-angled triangle * with sides x and y. * * Math.hypot(3, 4) #=> 5.0 */ static VALUE math_hypot(obj, x, y) VALUE obj, x, y; { Need_Float2(x, y); return rb_float_new(hypot(RFLOAT(x)->value, RFLOAT(y)->value)); } /* * call-seq: * Math.erf(x) => float * * Calculates the error function of x. */ static VALUE math_erf(obj, x) VALUE obj, x; { Need_Float(x); return rb_float_new(erf(RFLOAT(x)->value)); } /* * call-seq: * Math.erfc(x) => float * * Calculates the complementary error function of x. */ static VALUE math_erfc(obj, x) VALUE obj, x; { Need_Float(x); return rb_float_new(erfc(RFLOAT(x)->value)); } /* * The Math module contains module functions for basic * trigonometric and transcendental functions. See class * Float for a list of constants that * define Ruby's floating point accuracy. */ void Init_Math() { rb_mMath = rb_define_module("Math"); #ifdef M_PI rb_define_const(rb_mMath, "PI", rb_float_new(M_PI)); #else rb_define_const(rb_mMath, "PI", rb_float_new(atan(1.0)*4.0)); #endif #ifdef M_E rb_define_const(rb_mMath, "E", rb_float_new(M_E)); #else rb_define_const(rb_mMath, "E", rb_float_new(exp(1.0))); #endif rb_define_module_function(rb_mMath, "atan2", math_atan2, 2); rb_define_module_function(rb_mMath, "cos", math_cos, 1); rb_define_module_function(rb_mMath, "sin", math_sin, 1); rb_define_module_function(rb_mMath, "tan", math_tan, 1); rb_define_module_function(rb_mMath, "acos", math_acos, 1); rb_define_module_function(rb_mMath, "asin", math_asin, 1); rb_define_module_function(rb_mMath, "atan", math_atan, 1); rb_define_module_function(rb_mMath, "cosh", math_cosh, 1); rb_define_module_function(rb_mMath, "sinh", math_sinh, 1); rb_define_module_function(rb_mMath, "tanh", math_tanh, 1); rb_define_module_function(rb_mMath, "acosh", math_acosh, 1); rb_define_module_function(rb_mMath, "asinh", math_asinh, 1); rb_define_module_function(rb_mMath, "atanh", math_atanh, 1); rb_define_module_function(rb_mMath, "exp", math_exp, 1); rb_define_module_function(rb_mMath, "log", math_log, 1); rb_define_module_function(rb_mMath, "log10", math_log10, 1); rb_define_module_function(rb_mMath, "sqrt", math_sqrt, 1); rb_define_module_function(rb_mMath, "frexp", math_frexp, 1); rb_define_module_function(rb_mMath, "ldexp", math_ldexp, 2); rb_define_module_function(rb_mMath, "hypot", math_hypot, 2); rb_define_module_function(rb_mMath, "erf", math_erf, 1); rb_define_module_function(rb_mMath, "erfc", math_erfc, 1); } /********************************************************************** numeric.c - $Author: wyhaines $ $Date: 2009-07-14 17:05:27 +0200 (Tue, 14 Jul 2009) $ created at: Fri Aug 13 18:33:09 JST 1993 Copyright (C) 1993-2003 Yukihiro Matsumoto **********************************************************************/ #include "ruby.h" #include "env.h" #include #include #include #if defined(__FreeBSD__) && __FreeBSD__ < 4 #include #endif #ifdef HAVE_FLOAT_H #include #endif #ifdef HAVE_IEEEFP_H #include #endif /* use IEEE 64bit values if not defined */ #ifndef FLT_RADIX #define FLT_RADIX 2 #endif #ifndef FLT_ROUNDS #define FLT_ROUNDS 1 #endif #ifndef DBL_MIN #define DBL_MIN 2.2250738585072014e-308 #endif #ifndef DBL_MAX #define DBL_MAX 1.7976931348623157e+308 #endif #ifndef DBL_MIN_EXP #define DBL_MIN_EXP (-1021) #endif #ifndef DBL_MAX_EXP #define DBL_MAX_EXP 1024 #endif #ifndef DBL_MIN_10_EXP #define DBL_MIN_10_EXP (-307) #endif #ifndef DBL_MAX_10_EXP #define DBL_MAX_10_EXP 308 #endif #ifndef DBL_DIG #define DBL_DIG 15 #endif #ifndef DBL_MANT_DIG #define DBL_MANT_DIG 53 #endif #ifndef DBL_EPSILON #define DBL_EPSILON 2.2204460492503131e-16 #endif #ifndef HAVE_ROUND double round(x) double x; { double f; if (x > 0.0) { f = floor(x); x = f + (x - f >= 0.5); } else if (x < 0.0) { f = ceil(x); x = f - (f - x >= 0.5); } return x; } #endif static ID id_coerce, id_to_i, id_eq; VALUE rb_cNumeric; VALUE rb_cFloat; VALUE rb_cInteger; VALUE rb_cFixnum; VALUE rb_eZeroDivError; VALUE rb_eFloatDomainError; void rb_num_zerodiv() { rb_raise(rb_eZeroDivError, "divided by 0"); } /* * call-seq: * num.coerce(numeric) => array * * If aNumeric is the same type as num, returns an array * containing aNumeric and num. Otherwise, returns an * array with both aNumeric and num represented as * Float objects. This coercion mechanism is used by * Ruby to handle mixed-type numeric operations: it is intended to * find a compatible common type between the two operands of the operator. * * 1.coerce(2.5) #=> [2.5, 1.0] * 1.2.coerce(3) #=> [3.0, 1.2] * 1.coerce(2) #=> [2, 1] */ static VALUE num_coerce(x, y) VALUE x, y; { if (CLASS_OF(x) == CLASS_OF(y)) return rb_assoc_new(y, x); x = rb_Float(x); y = rb_Float(y); return rb_assoc_new(y, x); } static VALUE coerce_body(x) VALUE *x; { return rb_funcall(x[1], id_coerce, 1, x[0]); } static VALUE coerce_rescue(x) VALUE *x; { volatile VALUE v = rb_inspect(x[1]); rb_raise(rb_eTypeError, "%s can't be coerced into %s", rb_special_const_p(x[1])? RSTRING(v)->ptr: rb_obj_classname(x[1]), rb_obj_classname(x[0])); return Qnil; /* dummy */ } static int do_coerce(x, y, err) VALUE *x, *y; int err; { VALUE ary; VALUE a[2]; a[0] = *x; a[1] = *y; ary = rb_rescue(coerce_body, (VALUE)a, err?coerce_rescue:0, (VALUE)a); if (TYPE(ary) != T_ARRAY || RARRAY(ary)->len != 2) { if (err) { rb_raise(rb_eTypeError, "coerce must return [x, y]"); } return Qfalse; } *x = RARRAY(ary)->ptr[0]; *y = RARRAY(ary)->ptr[1]; return Qtrue; } VALUE rb_num_coerce_bin(x, y) VALUE x, y; { do_coerce(&x, &y, Qtrue); return rb_funcall(x, ruby_frame->orig_func, 1, y); } VALUE rb_num_coerce_cmp(x, y) VALUE x, y; { if (do_coerce(&x, &y, Qfalse)) return rb_funcall(x, ruby_frame->orig_func, 1, y); return Qnil; } VALUE rb_num_coerce_relop(x, y) VALUE x, y; { VALUE c, x0 = x, y0 = y; if (!do_coerce(&x, &y, Qfalse) || NIL_P(c = rb_funcall(x, ruby_frame->orig_func, 1, y))) { rb_cmperr(x0, y0); return Qnil; /* not reached */ } return c; } /* * Trap attempts to add methods to Numeric objects. Always * raises a TypeError */ static VALUE num_sadded(x, name) VALUE x, name; { ruby_frame = ruby_frame->prev; /* pop frame for "singleton_method_added" */ /* Numerics should be values; singleton_methods should not be added to them */ rb_raise(rb_eTypeError, "can't define singleton method \"%s\" for %s", rb_id2name(rb_to_id(name)), rb_obj_classname(x)); return Qnil; /* not reached */ } /* :nodoc: */ static VALUE num_init_copy(x, y) VALUE x, y; { /* Numerics are immutable values, which should not be copied */ rb_raise(rb_eTypeError, "can't copy %s", rb_obj_classname(x)); return Qnil; /* not reached */ } /* * call-seq: * +num => num * * Unary Plus---Returns the receiver's value. */ static VALUE num_uplus(num) VALUE num; { return num; } /* * call-seq: * -num => numeric * * Unary Minus---Returns the receiver's value, negated. */ static VALUE num_uminus(num) VALUE num; { VALUE zero; zero = INT2FIX(0); do_coerce(&zero, &num, Qtrue); return rb_funcall(zero, '-', 1, num); } /* * call-seq: * num.quo(numeric) => result * * Equivalent to Numeric#/, but overridden in subclasses. */ static VALUE num_quo(x, y) VALUE x, y; { return rb_funcall(x, '/', 1, y); } static VALUE num_floor(VALUE num); /* * call-seq: * num.div(numeric) => integer * * Uses / to perform division, then converts the result to * an integer. Numeric does not define the / * operator; this is left to subclasses. */ static VALUE num_div(x, y) VALUE x, y; { return num_floor(rb_funcall(x, '/', 1, y)); } /* * call-seq: * num.divmod( aNumeric ) -> anArray * * Returns an array containing the quotient and modulus obtained by * dividing num by aNumeric. If q, r = * x.divmod(y), then * * q = floor(float(x)/float(y)) * x = q*y + r * * The quotient is rounded toward -infinity, as shown in the following table: * * a | b | a.divmod(b) | a/b | a.modulo(b) | a.remainder(b) * ------+-----+---------------+---------+-------------+--------------- * 13 | 4 | 3, 1 | 3 | 1 | 1 * ------+-----+---------------+---------+-------------+--------------- * 13 | -4 | -4, -3 | -3 | -3 | 1 * ------+-----+---------------+---------+-------------+--------------- * -13 | 4 | -4, 3 | -4 | 3 | -1 * ------+-----+---------------+---------+-------------+--------------- * -13 | -4 | 3, -1 | 3 | -1 | -1 * ------+-----+---------------+---------+-------------+--------------- * 11.5 | 4 | 2, 3.5 | 2.875 | 3.5 | 3.5 * ------+-----+---------------+---------+-------------+--------------- * 11.5 | -4 | -3, -0.5 | -2.875 | -0.5 | 3.5 * ------+-----+---------------+---------+-------------+--------------- * -11.5 | 4 | -3, 0.5 | -2.875 | 0.5 | -3.5 * ------+-----+---------------+---------+-------------+--------------- * -11.5 | -4 | 2 -3.5 | 2.875 | -3.5 | -3.5 * * * Examples * 11.divmod(3) #=> [3, 2] * 11.divmod(-3) #=> [-4, -1] * 11.divmod(3.5) #=> [3, 0.5] * (-11).divmod(3.5) #=> [-4, 3.0] * (11.5).divmod(3.5) #=> [3, 1.0] */ static VALUE num_divmod(x, y) VALUE x, y; { return rb_assoc_new(num_div(x, y), rb_funcall(x, '%', 1, y)); } /* * call-seq: * num.modulo(numeric) => result * * Equivalent to * num.divmod(aNumeric)[1]. */ static VALUE num_modulo(x, y) VALUE x, y; { return rb_funcall(x, '%', 1, y); } /* * call-seq: * num.remainder(numeric) => result * * If num and numeric have different signs, returns * mod-numeric; otherwise, returns mod. In * both cases mod is the value * num.modulo(numeric). The * differences between remainder and modulo * (%) are shown in the table under Numeric#divmod. */ static VALUE num_remainder(x, y) VALUE x, y; { VALUE z = rb_funcall(x, '%', 1, y); if ((!rb_equal(z, INT2FIX(0))) && ((RTEST(rb_funcall(x, '<', 1, INT2FIX(0))) && RTEST(rb_funcall(y, '>', 1, INT2FIX(0)))) || (RTEST(rb_funcall(x, '>', 1, INT2FIX(0))) && RTEST(rb_funcall(y, '<', 1, INT2FIX(0)))))) { return rb_funcall(z, '-', 1, y); } return z; } /* * call-seq: * num.integer? -> true or false * * Returns true if num is an Integer * (including Fixnum and Bignum). */ static VALUE num_int_p(num) VALUE num; { return Qfalse; } /* * call-seq: * num.abs => num or numeric * * Returns the absolute value of num. * * 12.abs #=> 12 * (-34.56).abs #=> 34.56 * -34.56.abs #=> 34.56 */ static VALUE num_abs(num) VALUE num; { if (RTEST(rb_funcall(num, '<', 1, INT2FIX(0)))) { return rb_funcall(num, rb_intern("-@"), 0); } return num; } /* * call-seq: * num.zero? => true or false * * Returns true if num has a zero value. */ static VALUE num_zero_p(num) VALUE num; { if (rb_equal(num, INT2FIX(0))) { return Qtrue; } return Qfalse; } /* * call-seq: * num.nonzero? => num or nil * * Returns num if num is not zero, nil * otherwise. This behavior is useful when chaining comparisons: * * a = %w( z Bb bB bb BB a aA Aa AA A ) * b = a.sort {|a,b| (a.downcase <=> b.downcase).nonzero? || a <=> b } * b #=> ["A", "a", "AA", "Aa", "aA", "BB", "Bb", "bB", "bb", "z"] */ static VALUE num_nonzero_p(num) VALUE num; { if (RTEST(rb_funcall(num, rb_intern("zero?"), 0, 0))) { return Qnil; } return num; } /* * call-seq: * num.to_int => integer * * Invokes the child class's to_i method to convert * num to an integer. */ static VALUE num_to_int(num) VALUE num; { return rb_funcall(num, id_to_i, 0, 0); } /******************************************************************** * * Document-class: Float * * Float objects represent real numbers using the native * architecture's double-precision floating point representation. */ VALUE rb_float_new(d) double d; { NEWOBJ(flt, struct RFloat); OBJSETUP(flt, rb_cFloat, T_FLOAT); flt->value = d; return (VALUE)flt; } /* * call-seq: * flt.to_s => string * * Returns a string containing a representation of self. As well as a * fixed or exponential form of the number, the call may return * ``NaN'', ``Infinity'', and * ``-Infinity''. */ static VALUE flo_to_s(flt) VALUE flt; { char buf[32]; double value = RFLOAT(flt)->value; char *p, *e; if (isinf(value)) return rb_str_new2(value < 0 ? "-Infinity" : "Infinity"); else if(isnan(value)) return rb_str_new2("NaN"); sprintf(buf, "%#.15g", value); /* ensure to print decimal point */ if (!(e = strchr(buf, 'e'))) { e = buf + strlen(buf); } if (!ISDIGIT(e[-1])) { /* reformat if ended with decimal point (ex 111111111111111.) */ sprintf(buf, "%#.14e", value); if (!(e = strchr(buf, 'e'))) { e = buf + strlen(buf); } } p = e; while (p[-1]=='0' && ISDIGIT(p[-2])) p--; memmove(p, e, strlen(e)+1); return rb_str_new2(buf); } /* * MISSING: documentation */ static VALUE flo_coerce(x, y) VALUE x, y; { return rb_assoc_new(rb_Float(y), x); } /* * call-seq: * -float => float * * Returns float, negated. */ static VALUE flo_uminus(flt) VALUE flt; { return rb_float_new(-RFLOAT(flt)->value); } /* * call-seq: * float + other => float * * Returns a new float which is the sum of float * and other. */ static VALUE flo_plus(x, y) VALUE x, y; { switch (TYPE(y)) { case T_FIXNUM: return rb_float_new(RFLOAT(x)->value + (double)FIX2LONG(y)); case T_BIGNUM: return rb_float_new(RFLOAT(x)->value + rb_big2dbl(y)); case T_FLOAT: return rb_float_new(RFLOAT(x)->value + RFLOAT(y)->value); default: return rb_num_coerce_bin(x, y); } } /* * call-seq: * float + other => float * * Returns a new float which is the difference of float * and other. */ static VALUE flo_minus(x, y) VALUE x, y; { switch (TYPE(y)) { case T_FIXNUM: return rb_float_new(RFLOAT(x)->value - (double)FIX2LONG(y)); case T_BIGNUM: return rb_float_new(RFLOAT(x)->value - rb_big2dbl(y)); case T_FLOAT: return rb_float_new(RFLOAT(x)->value - RFLOAT(y)->value); default: return rb_num_coerce_bin(x, y); } } /* * call-seq: * float * other => float * * Returns a new float which is the product of float * and other. */ static VALUE flo_mul(x, y) VALUE x, y; { switch (TYPE(y)) { case T_FIXNUM: return rb_float_new(RFLOAT(x)->value * (double)FIX2LONG(y)); case T_BIGNUM: return rb_float_new(RFLOAT(x)->value * rb_big2dbl(y)); case T_FLOAT: return rb_float_new(RFLOAT(x)->value * RFLOAT(y)->value); default: return rb_num_coerce_bin(x, y); } } /* * call-seq: * float / other => float * * Returns a new float which is the result of dividing * float by other. */ static VALUE flo_div(x, y) VALUE x, y; { long f_y; double d; switch (TYPE(y)) { case T_FIXNUM: f_y = FIX2LONG(y); return rb_float_new(RFLOAT(x)->value / (double)f_y); case T_BIGNUM: d = rb_big2dbl(y); return rb_float_new(RFLOAT(x)->value / d); case T_FLOAT: return rb_float_new(RFLOAT(x)->value / RFLOAT(y)->value); default: return rb_num_coerce_bin(x, y); } } static void flodivmod(x, y, divp, modp) double x, y; double *divp, *modp; { double div, mod; #ifdef HAVE_FMOD mod = fmod(x, y); #else { double z; modf(x/y, &z); mod = x - z * y; } #endif if (isinf(x) && !isinf(y) && !isnan(y)) div = x; else div = (x - mod) / y; if (y*mod < 0) { mod += y; div -= 1.0; } if (modp) *modp = mod; if (divp) *divp = div; } /* * call-seq: * flt % other => float * flt.modulo(other) => float * * Return the modulo after division of flt by other. * * 6543.21.modulo(137) #=> 104.21 * 6543.21.modulo(137.24) #=> 92.9299999999996 */ static VALUE flo_mod(x, y) VALUE x, y; { double fy, mod; switch (TYPE(y)) { case T_FIXNUM: fy = (double)FIX2LONG(y); break; case T_BIGNUM: fy = rb_big2dbl(y); break; case T_FLOAT: fy = RFLOAT(y)->value; break; default: return rb_num_coerce_bin(x, y); } flodivmod(RFLOAT(x)->value, fy, 0, &mod); return rb_float_new(mod); } /* * call-seq: * flt.divmod(numeric) => array * * See Numeric#divmod. */ static VALUE flo_divmod(x, y) VALUE x, y; { double fy, div, mod, val; volatile VALUE a, b; switch (TYPE(y)) { case T_FIXNUM: fy = (double)FIX2LONG(y); break; case T_BIGNUM: fy = rb_big2dbl(y); break; case T_FLOAT: fy = RFLOAT(y)->value; break; default: return rb_num_coerce_bin(x, y); } flodivmod(RFLOAT(x)->value, fy, &div, &mod); if (FIXABLE(div)) { val = round(div); a = LONG2FIX(val); } else { a = rb_dbl2big(div); } b = rb_float_new(mod); return rb_assoc_new(a, b); } /* * call-seq: * * flt ** other => float * * Raises float the other power. */ static VALUE flo_pow(x, y) VALUE x, y; { switch (TYPE(y)) { case T_FIXNUM: return rb_float_new(pow(RFLOAT(x)->value, (double)FIX2LONG(y))); case T_BIGNUM: return rb_float_new(pow(RFLOAT(x)->value, rb_big2dbl(y))); case T_FLOAT: return rb_float_new(pow(RFLOAT(x)->value, RFLOAT(y)->value)); default: return rb_num_coerce_bin(x, y); } } /* * call-seq: * num.eql?(numeric) => true or false * * Returns true if num and numeric are the * same type and have equal values. * * 1 == 1.0 #=> true * 1.eql?(1.0) #=> false * (1.0).eql?(1.0) #=> true */ static VALUE num_eql(x, y) VALUE x, y; { if (TYPE(x) != TYPE(y)) return Qfalse; return rb_equal(x, y); } /* * call-seq: * num <=> other -> 0 or nil * * Returns zero if num equals other, nil * otherwise. */ static VALUE num_cmp(x, y) VALUE x, y; { if (x == y) return INT2FIX(0); return Qnil; } static VALUE num_equal(x, y) VALUE x, y; { if (x == y) return Qtrue; return rb_funcall(y, id_eq, 1, x); } /* * call-seq: * flt == obj => true or false * * Returns true only if obj has the same value * as flt. Contrast this with Float#eql?, which * requires obj to be a Float. * * 1.0 == 1 #=> true * */ static VALUE flo_eq(x, y) VALUE x, y; { volatile double a, b; switch (TYPE(y)) { case T_FIXNUM: b = FIX2LONG(y); break; case T_BIGNUM: b = rb_big2dbl(y); break; case T_FLOAT: b = RFLOAT(y)->value; if (isnan(b)) return Qfalse; break; default: return num_equal(x, y); } a = RFLOAT(x)->value; if (isnan(a)) return Qfalse; return (a == b)?Qtrue:Qfalse; } /* * call-seq: * flt.hash => integer * * Returns a hash code for this float. */ static VALUE flo_hash(num) VALUE num; { double d; char *c; int i, hash; d = RFLOAT(num)->value; if (d == 0) d = fabs(d); c = (char*)&d; for (hash=0, i=0; i b) return INT2FIX(1); if (a < b) return INT2FIX(-1); return Qnil; } /* * call-seq: * flt <=> numeric => -1, 0, +1 * * Returns -1, 0, or +1 depending on whether flt is less than, * equal to, or greater than numeric. This is the basis for the * tests in Comparable. */ static VALUE flo_cmp(x, y) VALUE x, y; { double a, b; a = RFLOAT(x)->value; switch (TYPE(y)) { case T_FIXNUM: b = (double)FIX2LONG(y); break; case T_BIGNUM: if (isinf(a)) { if (a > 0.0) return INT2FIX(1); else return INT2FIX(-1); } b = rb_big2dbl(y); break; case T_FLOAT: b = RFLOAT(y)->value; break; default: return rb_num_coerce_cmp(x, y); } return rb_dbl_cmp(a, b); } /* * call-seq: * flt > other => true or false * * true if flt is greater than other. */ static VALUE flo_gt(x, y) VALUE x, y; { double a, b; a = RFLOAT(x)->value; switch (TYPE(y)) { case T_FIXNUM: b = (double)FIX2LONG(y); break; case T_BIGNUM: b = rb_big2dbl(y); break; case T_FLOAT: b = RFLOAT(y)->value; if (isnan(b)) return Qfalse; break; default: return rb_num_coerce_relop(x, y); } if (isnan(a)) return Qfalse; return (a > b)?Qtrue:Qfalse; } /* * call-seq: * flt >= other => true or false * * true if flt is greater than * or equal to other. */ static VALUE flo_ge(x, y) VALUE x, y; { double a, b; a = RFLOAT(x)->value; switch (TYPE(y)) { case T_FIXNUM: b = (double)FIX2LONG(y); break; case T_BIGNUM: b = rb_big2dbl(y); break; case T_FLOAT: b = RFLOAT(y)->value; if (isnan(b)) return Qfalse; break; default: return rb_num_coerce_relop(x, y); } if (isnan(a)) return Qfalse; return (a >= b)?Qtrue:Qfalse; } /* * call-seq: * flt < other => true or false * * true if flt is less than other. */ static VALUE flo_lt(x, y) VALUE x, y; { double a, b; a = RFLOAT(x)->value; switch (TYPE(y)) { case T_FIXNUM: b = (double)FIX2LONG(y); break; case T_BIGNUM: b = rb_big2dbl(y); break; case T_FLOAT: b = RFLOAT(y)->value; if (isnan(b)) return Qfalse; break; default: return rb_num_coerce_relop(x, y); } if (isnan(a)) return Qfalse; return (a < b)?Qtrue:Qfalse; } /* * call-seq: * flt <= other => true or false * * true if flt is less than * or equal to other. */ static VALUE flo_le(x, y) VALUE x, y; { double a, b; a = RFLOAT(x)->value; switch (TYPE(y)) { case T_FIXNUM: b = (double)FIX2LONG(y); break; case T_BIGNUM: b = rb_big2dbl(y); break; case T_FLOAT: b = RFLOAT(y)->value; if (isnan(b)) return Qfalse; break; default: return rb_num_coerce_relop(x, y); } if (isnan(a)) return Qfalse; return (a <= b)?Qtrue:Qfalse; } /* * call-seq: * flt.eql?(obj) => true or false * * Returns true only if obj is a * Float with the same value as flt. Contrast this * with Float#==, which performs type conversions. * * 1.0.eql?(1) #=> false */ static VALUE flo_eql(x, y) VALUE x, y; { if (TYPE(y) == T_FLOAT) { double a = RFLOAT(x)->value; double b = RFLOAT(y)->value; if (isnan(a) || isnan(b)) return Qfalse; if (a == b) return Qtrue; } return Qfalse; } /* * call-seq: * flt.to_f => flt * * As flt is already a float, returns self. */ static VALUE flo_to_f(num) VALUE num; { return num; } /* * call-seq: * flt.abs => float * * Returns the absolute value of flt. * * (-34.56).abs #=> 34.56 * -34.56.abs #=> 34.56 * */ static VALUE flo_abs(flt) VALUE flt; { double val = fabs(RFLOAT(flt)->value); return rb_float_new(val); } /* * call-seq: * flt.zero? -> true or false * * Returns true if flt is 0.0. * */ static VALUE flo_zero_p(num) VALUE num; { if (RFLOAT(num)->value == 0.0) { return Qtrue; } return Qfalse; } /* * call-seq: * flt.nan? -> true or false * * Returns true if flt is an invalid IEEE floating * point number. * * a = -1.0 #=> -1.0 * a.nan? #=> false * a = 0.0/0.0 #=> NaN * a.nan? #=> true */ static VALUE flo_is_nan_p(num) VALUE num; { double value = RFLOAT(num)->value; return isnan(value) ? Qtrue : Qfalse; } /* * call-seq: * flt.infinite? -> nil, -1, +1 * * Returns nil, -1, or +1 depending on whether flt * is finite, -infinity, or +infinity. * * (0.0).infinite? #=> nil * (-1.0/0.0).infinite? #=> -1 * (+1.0/0.0).infinite? #=> 1 */ static VALUE flo_is_infinite_p(num) VALUE num; { double value = RFLOAT(num)->value; if (isinf(value)) { return INT2FIX( value < 0 ? -1 : 1 ); } return Qnil; } /* * call-seq: * flt.finite? -> true or false * * Returns true if flt is a valid IEEE floating * point number (it is not infinite, and nan? is * false). * */ static VALUE flo_is_finite_p(num) VALUE num; { double value = RFLOAT(num)->value; #if HAVE_FINITE if (!finite(value)) return Qfalse; #else if (isinf(value) || isnan(value)) return Qfalse; #endif return Qtrue; } /* * call-seq: * flt.floor => integer * * Returns the largest integer less than or equal to flt. * * 1.2.floor #=> 1 * 2.0.floor #=> 2 * (-1.2).floor #=> -2 * (-2.0).floor #=> -2 */ static VALUE flo_floor(num) VALUE num; { double f = floor(RFLOAT(num)->value); long val; if (!FIXABLE(f)) { return rb_dbl2big(f); } val = f; return LONG2FIX(val); } /* * call-seq: * flt.ceil => integer * * Returns the smallest Integer greater than or equal to * flt. * * 1.2.ceil #=> 2 * 2.0.ceil #=> 2 * (-1.2).ceil #=> -1 * (-2.0).ceil #=> -2 */ static VALUE flo_ceil(num) VALUE num; { double f = ceil(RFLOAT(num)->value); long val; if (!FIXABLE(f)) { return rb_dbl2big(f); } val = f; return LONG2FIX(val); } /* * call-seq: * flt.round => integer * * Rounds flt to the nearest integer. Equivalent to: * * def round * return (self+0.5).floor if self > 0.0 * return (self-0.5).ceil if self < 0.0 * return 0 * end * * 1.5.round #=> 2 * (-1.5).round #=> -2 * */ static VALUE flo_round(num) VALUE num; { double f = RFLOAT(num)->value; long val; f = round(f); if (!FIXABLE(f)) { return rb_dbl2big(f); } val = f; return LONG2FIX(val); } /* * call-seq: * flt.to_i => integer * flt.to_int => integer * flt.truncate => integer * * Returns flt truncated to an Integer. */ static VALUE flo_truncate(num) VALUE num; { double f = RFLOAT(num)->value; long val; if (f > 0.0) f = floor(f); if (f < 0.0) f = ceil(f); if (!FIXABLE(f)) { return rb_dbl2big(f); } val = f; return LONG2FIX(val); } /* * call-seq: * num.floor => integer * * Returns the largest integer less than or equal to num. * Numeric implements this by converting anInteger * to a Float and invoking Float#floor. * * 1.floor #=> 1 * (-1).floor #=> -1 */ static VALUE num_floor(num) VALUE num; { return flo_floor(rb_Float(num)); } /* * call-seq: * num.ceil => integer * * Returns the smallest Integer greater than or equal to * num. Class Numeric achieves this by converting * itself to a Float then invoking * Float#ceil. * * 1.ceil #=> 1 * 1.2.ceil #=> 2 * (-1.2).ceil #=> -1 * (-1.0).ceil #=> -1 */ static VALUE num_ceil(num) VALUE num; { return flo_ceil(rb_Float(num)); } /* * call-seq: * num.round => integer * * Rounds num to the nearest integer. Numeric * implements this by converting itself to a * Float and invoking Float#round. */ static VALUE num_round(num) VALUE num; { return flo_round(rb_Float(num)); } /* * call-seq: * num.truncate => integer * * Returns num truncated to an integer. Numeric * implements this by converting its value to a float and invoking * Float#truncate. */ static VALUE num_truncate(num) VALUE num; { return flo_truncate(rb_Float(num)); } /* * call-seq: * num.step(limit, step ) {|i| block } => num * * Invokes block with the sequence of numbers starting at * num, incremented by step on each call. The loop * finishes when the value to be passed to the block is greater than * limit (if step is positive) or less than * limit (if step is negative). If all the arguments are * integers, the loop operates using an integer counter. If any of the * arguments are floating point numbers, all are converted to floats, * and the loop is executed floor(n + n*epsilon)+ 1 times, * where n = (limit - num)/step. Otherwise, the loop * starts at num, uses either the < or * > operator to compare the counter against * limit, and increments itself using the + * operator. * * 1.step(10, 2) { |i| print i, " " } * Math::E.step(Math::PI, 0.2) { |f| print f, " " } * * produces: * * 1 3 5 7 9 * 2.71828182845905 2.91828182845905 3.11828182845905 */ static VALUE num_step(argc, argv, from) int argc; VALUE *argv; VALUE from; { VALUE to, step; if (argc == 1) { to = argv[0]; step = INT2FIX(1); } else { if (argc == 2) { to = argv[0]; step = argv[1]; } else { rb_raise(rb_eArgError, "wrong number of arguments"); } if (rb_equal(step, INT2FIX(0))) { rb_raise(rb_eArgError, "step can't be 0"); } } if (FIXNUM_P(from) && FIXNUM_P(to) && FIXNUM_P(step)) { long i, end, diff; i = FIX2LONG(from); end = FIX2LONG(to); diff = FIX2LONG(step); if (diff > 0) { while (i <= end) { rb_yield(LONG2FIX(i)); i += diff; } } else { while (i >= end) { rb_yield(LONG2FIX(i)); i += diff; } } } else if (TYPE(from) == T_FLOAT || TYPE(to) == T_FLOAT || TYPE(step) == T_FLOAT) { const double epsilon = DBL_EPSILON; double beg = NUM2DBL(from); double end = NUM2DBL(to); double unit = NUM2DBL(step); double n = (end - beg)/unit; double err = (fabs(beg) + fabs(end) + fabs(end-beg)) / fabs(unit) * epsilon; long i; if (err>0.5) err=0.5; n = floor(n + err) + 1; for (i=0; i', 1, INT2FIX(0)))) { cmp = '>'; } else { cmp = '<'; } for (;;) { if (RTEST(rb_funcall(i, cmp, 1, to))) break; rb_yield(i); i = rb_funcall(i, '+', 1, step); } } return from; } long rb_num2long(val) VALUE val; { again: if (NIL_P(val)) { rb_raise(rb_eTypeError, "no implicit conversion from nil to integer"); } if (FIXNUM_P(val)) return FIX2LONG(val); switch (TYPE(val)) { case T_FLOAT: if (RFLOAT(val)->value <= (double)LONG_MAX && RFLOAT(val)->value >= (double)LONG_MIN) { return (long)(RFLOAT(val)->value); } else { char buf[24]; char *s; sprintf(buf, "%-.10g", RFLOAT(val)->value); if ((s = strchr(buf, ' ')) != 0) *s = '\0'; rb_raise(rb_eRangeError, "float %s out of range of integer", buf); } case T_BIGNUM: return rb_big2long(val); default: val = rb_to_int(val); goto again; } } unsigned long rb_num2ulong(val) VALUE val; { if (TYPE(val) == T_BIGNUM) { return rb_big2ulong(val); } return (unsigned long)rb_num2long(val); } #if SIZEOF_INT < SIZEOF_LONG static void check_int(num) long num; { const char *s; if (num < INT_MIN) { s = "small"; } else if (num > INT_MAX) { s = "big"; } else { return; } rb_raise(rb_eRangeError, "integer %ld too %s to convert to `int'", num, s); } static void check_uint(num, sign) unsigned long num; VALUE sign; { static const unsigned long mask = ~(unsigned long)UINT_MAX; if (RTEST(sign)) { /* minus */ if ((num & mask) != mask || (num & ~mask) <= INT_MAX + 1UL) rb_raise(rb_eRangeError, "integer %ld too small to convert to `unsigned int'", num); } else { /* plus */ if ((num & mask) != 0) rb_raise(rb_eRangeError, "integer %lu too big to convert to `unsigned int'", num); } } long rb_num2int(val) VALUE val; { long num = rb_num2long(val); check_int(num); return num; } long rb_fix2int(val) VALUE val; { long num = FIXNUM_P(val)?FIX2LONG(val):rb_num2long(val); check_int(num); return num; } unsigned long rb_num2uint(val) VALUE val; { unsigned long num = rb_num2ulong(val); check_uint(num, rb_funcall(val, '<', 1, INT2FIX(0))); return num; } unsigned long rb_fix2uint(val) VALUE val; { unsigned long num; if (!FIXNUM_P(val)) { return rb_num2uint(val); } num = FIX2ULONG(val); check_uint(num, rb_funcall(val, '<', 1, INT2FIX(0))); return num; } #else long rb_num2int(val) VALUE val; { return rb_num2long(val); } long rb_fix2int(val) VALUE val; { return FIX2INT(val); } #endif VALUE rb_num2fix(val) VALUE val; { long v; if (FIXNUM_P(val)) return val; v = rb_num2long(val); if (!FIXABLE(v)) rb_raise(rb_eRangeError, "integer %ld out of range of fixnum", v); return LONG2FIX(v); } #if HAVE_LONG_LONG LONG_LONG rb_num2ll(val) VALUE val; { if (NIL_P(val)) { rb_raise(rb_eTypeError, "no implicit conversion from nil"); } if (FIXNUM_P(val)) return (LONG_LONG)FIX2LONG(val); switch (TYPE(val)) { case T_FLOAT: if (RFLOAT(val)->value <= (double)LLONG_MAX && RFLOAT(val)->value >= (double)LLONG_MIN) { return (LONG_LONG)(RFLOAT(val)->value); } else { char buf[24]; char *s; sprintf(buf, "%-.10g", RFLOAT(val)->value); if ((s = strchr(buf, ' ')) != 0) *s = '\0'; rb_raise(rb_eRangeError, "float %s out of range of long long", buf); } case T_BIGNUM: return rb_big2ll(val); case T_STRING: rb_raise(rb_eTypeError, "no implicit conversion from string"); return Qnil; /* not reached */ case T_TRUE: case T_FALSE: rb_raise(rb_eTypeError, "no implicit conversion from boolean"); return Qnil; /* not reached */ default: val = rb_to_int(val); return NUM2LL(val); } } unsigned LONG_LONG rb_num2ull(val) VALUE val; { if (TYPE(val) == T_BIGNUM) { return rb_big2ull(val); } return (unsigned LONG_LONG)rb_num2ll(val); } #endif /* HAVE_LONG_LONG */ /* * Document-class: Integer * * Integer is the basis for the two concrete classes that * hold whole numbers, Bignum and Fixnum. * */ /* * call-seq: * int.to_i => int * int.to_int => int * int.floor => int * int.ceil => int * int.round => int * int.truncate => int * * As int is already an Integer, all these * methods simply return the receiver. */ static VALUE int_to_i(num) VALUE num; { return num; } /* * call-seq: * int.integer? -> true * * Always returns true. */ static VALUE int_int_p(num) VALUE num; { return Qtrue; } /* * call-seq: * int.next => integer * int.succ => integer * * Returns the Integer equal to int + 1. * * 1.next #=> 2 * (-1).next #=> 0 */ static VALUE int_succ(num) VALUE num; { if (FIXNUM_P(num)) { long i = FIX2LONG(num) + 1; return LONG2NUM(i); } return rb_funcall(num, '+', 1, INT2FIX(1)); } /* * call-seq: * int.chr => string * * Returns a string containing the ASCII character represented by the * receiver's value. * * 65.chr #=> "A" * ?a.chr #=> "a" * 230.chr #=> "\346" */ static VALUE int_chr(num) VALUE num; { char c; long i = NUM2LONG(num); if (i < 0 || 0xff < i) rb_raise(rb_eRangeError, "%ld out of char range", i); c = i; return rb_str_new(&c, 1); } /******************************************************************** * * Document-class: Fixnum * * A Fixnum holds Integer values that can be * represented in a native machine word (minus 1 bit). If any operation * on a Fixnum exceeds this range, the value is * automatically converted to a Bignum. * * Fixnum objects have immediate value. This means that * when they are assigned or passed as parameters, the actual object is * passed, rather than a reference to that object. Assignment does not * alias Fixnum objects. There is effectively only one * Fixnum object instance for any given integer value, so, * for example, you cannot add a singleton method to a * Fixnum. */ /* * call-seq: * Fixnum.induced_from(obj) => fixnum * * Convert obj to a Fixnum. Works with numeric parameters. * Also works with Symbols, but this is deprecated. */ static VALUE rb_fix_induced_from(klass, x) VALUE klass, x; { return rb_num2fix(x); } /* * call-seq: * Integer.induced_from(obj) => fixnum, bignum * * Convert obj to an Integer. */ static VALUE rb_int_induced_from(klass, x) VALUE klass, x; { switch (TYPE(x)) { case T_FIXNUM: case T_BIGNUM: return x; case T_FLOAT: return rb_funcall(x, id_to_i, 0); default: rb_raise(rb_eTypeError, "failed to convert %s into Integer", rb_obj_classname(x)); } } /* * call-seq: * Float.induced_from(obj) => float * * Convert obj to a float. */ static VALUE rb_flo_induced_from(klass, x) VALUE klass, x; { switch (TYPE(x)) { case T_FIXNUM: case T_BIGNUM: return rb_funcall(x, rb_intern("to_f"), 0); case T_FLOAT: return x; default: rb_raise(rb_eTypeError, "failed to convert %s into Float", rb_obj_classname(x)); } } /* * call-seq: * -fix => integer * * Negates fix (which might return a Bignum). */ static VALUE fix_uminus(num) VALUE num; { return LONG2NUM(-FIX2LONG(num)); } VALUE rb_fix2str(x, base) VALUE x; int base; { extern const char ruby_digitmap[]; char buf[SIZEOF_LONG*CHAR_BIT + 2], *b = buf + sizeof buf; long val = FIX2LONG(x); int neg = 0; if (base < 2 || 36 < base) { rb_raise(rb_eArgError, "illegal radix %d", base); } if (val == 0) { return rb_str_new2("0"); } if (val < 0) { val = -val; neg = 1; } *--b = '\0'; do { *--b = ruby_digitmap[(int)(val % base)]; } while (val /= base); if (neg) { *--b = '-'; } return rb_str_new2(b); } /* * call-seq: * fix.to_s( base=10 ) -> aString * * Returns a string containing the representation of fix radix * base (between 2 and 36). * * 12345.to_s #=> "12345" * 12345.to_s(2) #=> "11000000111001" * 12345.to_s(8) #=> "30071" * 12345.to_s(10) #=> "12345" * 12345.to_s(16) #=> "3039" * 12345.to_s(36) #=> "9ix" * */ static VALUE fix_to_s(argc, argv, x) int argc; VALUE *argv; VALUE x; { VALUE b; int base; rb_scan_args(argc, argv, "01", &b); if (argc == 0) base = 10; else base = NUM2INT(b); return rb_fix2str(x, base); } /* * call-seq: * fix + numeric => numeric_result * * Performs addition: the class of the resulting object depends on * the class of numeric and on the magnitude of the * result. */ static VALUE fix_plus(x, y) VALUE x, y; { if (FIXNUM_P(y)) { long a, b, c; VALUE r; a = FIX2LONG(x); b = FIX2LONG(y); c = a + b; r = LONG2NUM(c); return r; } if (TYPE(y) == T_FLOAT) { return rb_float_new((double)FIX2LONG(x) + RFLOAT(y)->value); } return rb_num_coerce_bin(x, y); } /* * call-seq: * fix - numeric => numeric_result * * Performs subtraction: the class of the resulting object depends on * the class of numeric and on the magnitude of the * result. */ static VALUE fix_minus(x, y) VALUE x, y; { if (FIXNUM_P(y)) { long a, b, c; VALUE r; a = FIX2LONG(x); b = FIX2LONG(y); c = a - b; r = LONG2NUM(c); return r; } if (TYPE(y) == T_FLOAT) { return rb_float_new((double)FIX2LONG(x) - RFLOAT(y)->value); } return rb_num_coerce_bin(x, y); } /* * call-seq: * fix * numeric => numeric_result * * Performs multiplication: the class of the resulting object depends on * the class of numeric and on the magnitude of the * result. */ static VALUE fix_mul(x, y) VALUE x, y; { if (FIXNUM_P(y)) { #ifdef __HP_cc /* avoids an optimization bug of HP aC++/ANSI C B3910B A.06.05 [Jul 25 2005] */ volatile #endif long a, b, c; VALUE r; a = FIX2LONG(x); if (a == 0) return x; b = FIX2LONG(y); c = a * b; r = LONG2FIX(c); if (FIX2LONG(r) != c || c/a != b) { r = rb_big_mul(rb_int2big(a), rb_int2big(b)); } return r; } if (TYPE(y) == T_FLOAT) { return rb_float_new((double)FIX2LONG(x) * RFLOAT(y)->value); } return rb_num_coerce_bin(x, y); } static void fixdivmod(x, y, divp, modp) long x, y; long *divp, *modp; { long div, mod; if (y == 0) rb_num_zerodiv(); if (y < 0) { if (x < 0) div = -x / -y; else div = - (x / -y); } else { if (x < 0) div = - (-x / y); else div = x / y; } mod = x - div*y; if ((mod < 0 && y > 0) || (mod > 0 && y < 0)) { mod += y; div -= 1; } if (divp) *divp = div; if (modp) *modp = mod; } /* * call-seq: * fix.quo(numeric) => float * * Returns the floating point result of dividing fix by * numeric. * * 654321.quo(13731) #=> 47.6528293642124 * 654321.quo(13731.24) #=> 47.6519964693647 * */ static VALUE fix_quo(x, y) VALUE x, y; { if (FIXNUM_P(y)) { return rb_float_new((double)FIX2LONG(x) / (double)FIX2LONG(y)); } return rb_num_coerce_bin(x, y); } /* * call-seq: * fix / numeric => numeric_result * fix.div(numeric) => numeric_result * * Performs division: the class of the resulting object depends on * the class of numeric and on the magnitude of the * result. */ static VALUE fix_div(x, y) VALUE x, y; { if (FIXNUM_P(y)) { long div; fixdivmod(FIX2LONG(x), FIX2LONG(y), &div, 0); return LONG2NUM(div); } return rb_num_coerce_bin(x, y); } /* * call-seq: * fix % other => Numeric * fix.modulo(other) => Numeric * * Returns fix modulo other. * See Numeric.divmod for more information. */ static VALUE fix_mod(x, y) VALUE x, y; { if (FIXNUM_P(y)) { long mod; fixdivmod(FIX2LONG(x), FIX2LONG(y), 0, &mod); return LONG2NUM(mod); } return rb_num_coerce_bin(x, y); } /* * call-seq: * fix.divmod(numeric) => array * * See Numeric#divmod. */ static VALUE fix_divmod(x, y) VALUE x, y; { if (FIXNUM_P(y)) { long div, mod; fixdivmod(FIX2LONG(x), FIX2LONG(y), &div, &mod); return rb_assoc_new(LONG2NUM(div), LONG2NUM(mod)); } return rb_num_coerce_bin(x, y); } static VALUE int_even_p(VALUE num) { if (rb_funcall(num, '%', 1, INT2FIX(2)) == INT2FIX(0)) { return Qtrue; } return Qfalse; } /* * call-seq: * fix ** other => Numeric * * Raises fix to the other power, which may * be negative or fractional. * * 2 ** 3 #=> 8 * 2 ** -1 #=> 0.5 * 2 ** 0.5 #=> 1.4142135623731 */ static VALUE fix_pow(x, y) VALUE x, y; { static const double zero = 0.0; long a = FIX2LONG(x); if (FIXNUM_P(y)) { long b; b = FIX2LONG(y); if (b == 0) return INT2FIX(1); if (b == 1) return x; a = FIX2LONG(x); if (a == 0) { if (b > 0) return INT2FIX(0); return rb_float_new(1.0 / zero); } if (a == 1) return INT2FIX(1); if (a == -1) { if (b % 2 == 0) return INT2FIX(1); else return INT2FIX(-1); } if (b > 0) { return rb_big_pow(rb_int2big(a), y); } return rb_float_new(pow((double)a, (double)b)); } switch (TYPE(y)) { case T_BIGNUM: if (a == 0) return INT2FIX(0); if (a == 1) return INT2FIX(1); if (a == -1) { if (int_even_p(y)) return INT2FIX(1); else return INT2FIX(-1); } x = rb_int2big(FIX2LONG(x)); return rb_big_pow(x, y); case T_FLOAT: if (a == 0) { return rb_float_new(RFLOAT(y)->value < 0 ? (1.0 / zero) : 0.0); } if (a == 1) return rb_float_new(1.0); return rb_float_new(pow((double)a, RFLOAT(y)->value)); default: return rb_num_coerce_bin(x, y); } } /* * call-seq: * fix == other * * Return true if fix equals other * numerically. * * 1 == 2 #=> false * 1 == 1.0 #=> true */ static VALUE fix_equal(x, y) VALUE x, y; { if (x == y) return Qtrue; if (FIXNUM_P(y)) return Qfalse; return num_equal(x, y); } /* * call-seq: * fix <=> numeric => -1, 0, +1 * * Comparison---Returns -1, 0, or +1 depending on whether fix is * less than, equal to, or greater than numeric. This is the * basis for the tests in Comparable. */ static VALUE fix_cmp(x, y) VALUE x, y; { if (x == y) return INT2FIX(0); if (FIXNUM_P(y)) { long a = FIX2LONG(x), b = FIX2LONG(y); if (a > b) return INT2FIX(1); return INT2FIX(-1); } else { return rb_num_coerce_cmp(x, y); } } /* * call-seq: * fix > other => true or false * * Returns true if the value of fix is * greater than that of other. */ static VALUE fix_gt(x, y) VALUE x, y; { if (FIXNUM_P(y)) { long a = FIX2LONG(x), b = FIX2LONG(y); if (a > b) return Qtrue; return Qfalse; } else { return rb_num_coerce_relop(x, y); } } /* * call-seq: * fix >= other => true or false * * Returns true if the value of fix is * greater than or equal to that of other. */ static VALUE fix_ge(x, y) VALUE x, y; { if (FIXNUM_P(y)) { long a = FIX2LONG(x), b = FIX2LONG(y); if (a >= b) return Qtrue; return Qfalse; } else { return rb_num_coerce_relop(x, y); } } /* * call-seq: * fix < other => true or false * * Returns true if the value of fix is * less than that of other. */ static VALUE fix_lt(x, y) VALUE x, y; { if (FIXNUM_P(y)) { long a = FIX2LONG(x), b = FIX2LONG(y); if (a < b) return Qtrue; return Qfalse; } else { return rb_num_coerce_relop(x, y); } } /* * call-seq: * fix <= other => true or false * * Returns true if the value of fix is * less thanor equal to that of other. */ static VALUE fix_le(x, y) VALUE x, y; { if (FIXNUM_P(y)) { long a = FIX2LONG(x), b = FIX2LONG(y); if (a <= b) return Qtrue; return Qfalse; } else { return rb_num_coerce_relop(x, y); } } /* * call-seq: * ~fix => integer * * One's complement: returns a number where each bit is flipped. */ static VALUE fix_rev(num) VALUE num; { long val = FIX2LONG(num); val = ~val; return LONG2NUM(val); } static VALUE fix_coerce(x) VALUE x; { while (!FIXNUM_P(x) && TYPE(x) != T_BIGNUM) { x = rb_to_int(x); } return x; } /* * call-seq: * fix & other => integer * * Bitwise AND. */ static VALUE fix_and(x, y) VALUE x, y; { long val; if (!FIXNUM_P(y = fix_coerce(y))) { return rb_big_and(y, x); } val = FIX2LONG(x) & FIX2LONG(y); return LONG2NUM(val); } /* * call-seq: * fix | other => integer * * Bitwise OR. */ static VALUE fix_or(x, y) VALUE x, y; { long val; if (!FIXNUM_P(y = fix_coerce(y))) { return rb_big_or(y, x); } val = FIX2LONG(x) | FIX2LONG(y); return LONG2NUM(val); } /* * call-seq: * fix ^ other => integer * * Bitwise EXCLUSIVE OR. */ static VALUE fix_xor(x, y) VALUE x, y; { long val; if (!FIXNUM_P(y = fix_coerce(y))) { return rb_big_xor(y, x); } val = FIX2LONG(x) ^ FIX2LONG(y); return LONG2NUM(val); } static VALUE fix_lshift _((long, unsigned long)); static VALUE fix_rshift _((long, unsigned long)); /* * call-seq: * fix << count => integer * * Shifts _fix_ left _count_ positions (right if _count_ is negative). */ static VALUE rb_fix_lshift(x, y) VALUE x, y; { long val, width; val = NUM2LONG(x); if (!FIXNUM_P(y)) return rb_big_lshift(rb_int2big(val), y); width = FIX2LONG(y); if (width < 0) return fix_rshift(val, (unsigned long)-width); return fix_lshift(val, width); } static VALUE fix_lshift(val, width) long val; unsigned long width; { if (width > (sizeof(VALUE)*CHAR_BIT-1) || ((unsigned long)val)>>(sizeof(VALUE)*CHAR_BIT-1-width) > 0) { return rb_big_lshift(rb_int2big(val), ULONG2NUM(width)); } val = val << width; return LONG2NUM(val); } /* * call-seq: * fix >> count => integer * * Shifts _fix_ right _count_ positions (left if _count_ is negative). */ static VALUE rb_fix_rshift(x, y) VALUE x, y; { long i, val; val = FIX2LONG(x); if (!FIXNUM_P(y)) return rb_big_rshift(rb_int2big(val), y); i = FIX2LONG(y); if (i == 0) return x; if (i < 0) return fix_lshift(val, (unsigned long)-i); return fix_rshift(val, i); } static VALUE fix_rshift(long val, unsigned long i) { if (i >= sizeof(long)*CHAR_BIT-1) { if (val < 0) return INT2FIX(-1); return INT2FIX(0); } val = RSHIFT(val, i); return LONG2FIX(val); } /* * call-seq: * fix[n] => 0, 1 * * Bit Reference---Returns the nth bit in the binary * representation of fix, where fix[0] is the least * significant bit. * * a = 0b11001100101010 * 30.downto(0) do |n| print a[n] end * * produces: * * 0000000000000000011001100101010 */ static VALUE fix_aref(fix, idx) VALUE fix, idx; { long val = FIX2LONG(fix); long i; if (!FIXNUM_P(idx = fix_coerce(idx))) { idx = rb_big_norm(idx); if (!FIXNUM_P(idx)) { if (!RBIGNUM(idx)->sign || val >= 0) return INT2FIX(0); return INT2FIX(1); } } i = FIX2LONG(idx); if (i < 0) return INT2FIX(0); if (sizeof(VALUE)*CHAR_BIT-1 < i) { if (val < 0) return INT2FIX(1); return INT2FIX(0); } if (val & (1L< float * * Converts fix to a Float. * */ static VALUE fix_to_f(num) VALUE num; { double val; val = (double)FIX2LONG(num); return rb_float_new(val); } /* * call-seq: * fix.abs -> aFixnum * * Returns the absolute value of fix. * * -12345.abs #=> 12345 * 12345.abs #=> 12345 * */ static VALUE fix_abs(fix) VALUE fix; { long i = FIX2LONG(fix); if (i < 0) i = -i; return LONG2NUM(i); } /* * call-seq: * fix.id2name -> string or nil * * Returns the name of the object whose symbol id is fix. If * there is no symbol in the symbol table with this value, returns * nil. id2name has nothing to do with the * Object.id method. See also Fixnum#to_sym, * String#intern, and class Symbol. * * symbol = :@inst_var #=> :@inst_var * id = symbol.to_i #=> 9818 * id.id2name #=> "@inst_var" */ static VALUE fix_id2name(fix) VALUE fix; { char *name = rb_id2name(FIX2UINT(fix)); if (name) return rb_str_new2(name); return Qnil; } /* * call-seq: * fix.to_sym -> aSymbol * * Returns the symbol whose integer value is fix. See also * Fixnum#id2name. * * fred = :fred.to_i * fred.id2name #=> "fred" * fred.to_sym #=> :fred */ static VALUE fix_to_sym(fix) VALUE fix; { ID id = FIX2UINT(fix); if (rb_id2name(id)) { return ID2SYM(id); } return Qnil; } /* * call-seq: * fix.size -> fixnum * * Returns the number of bytes in the machine representation * of a Fixnum. * * 1.size #=> 4 * -1.size #=> 4 * 2147483647.size #=> 4 */ static VALUE fix_size(fix) VALUE fix; { return INT2FIX(sizeof(long)); } /* * call-seq: * int.upto(limit) {|i| block } => int * * Iterates block, passing in integer values from int * up to and including limit. * * 5.upto(10) { |i| print i, " " } * * produces: * * 5 6 7 8 9 10 */ static VALUE int_upto(from, to) VALUE from, to; { if (FIXNUM_P(from) && FIXNUM_P(to)) { long i, end; end = FIX2LONG(to); for (i = FIX2LONG(from); i <= end; i++) { rb_yield(LONG2FIX(i)); } } else { VALUE i = from, c; while (!(c = rb_funcall(i, '>', 1, to))) { rb_yield(i); i = rb_funcall(i, '+', 1, INT2FIX(1)); } if (NIL_P(c)) rb_cmperr(i, to); } return from; } /* * call-seq: * int.downto(limit) {|i| block } => int * * Iterates block, passing decreasing values from int * down to and including limit. * * 5.downto(1) { |n| print n, ".. " } * print " Liftoff!\n" * * produces: * * 5.. 4.. 3.. 2.. 1.. Liftoff! */ static VALUE int_downto(from, to) VALUE from, to; { if (FIXNUM_P(from) && FIXNUM_P(to)) { long i, end; end = FIX2LONG(to); for (i=FIX2LONG(from); i >= end; i--) { rb_yield(LONG2FIX(i)); } } else { VALUE i = from, c; while (!(c = rb_funcall(i, '<', 1, to))) { rb_yield(i); i = rb_funcall(i, '-', 1, INT2FIX(1)); } if (NIL_P(c)) rb_cmperr(i, to); } return from; } /* * call-seq: * int.times {|i| block } => int * * Iterates block int times, passing in values from zero to * int - 1. * * 5.times do |i| * print i, " " * end * * produces: * * 0 1 2 3 4 */ static VALUE int_dotimes(num) VALUE num; { if (FIXNUM_P(num)) { long i, end; end = FIX2LONG(num); for (i=0; i true or false * * Returns true if fix is zero. * */ static VALUE fix_zero_p(num) VALUE num; { if (FIX2LONG(num) == 0) { return Qtrue; } return Qfalse; } void Init_Numeric() { #if defined(__FreeBSD__) && __FreeBSD__ < 4 /* allow divide by zero -- Inf */ fpsetmask(fpgetmask() & ~(FP_X_DZ|FP_X_INV|FP_X_OFL)); #elif defined(_UNICOSMP) /* Turn off floating point exceptions for divide by zero, etc. */ _set_Creg(0, 0); #elif defined(__BORLANDC__) /* Turn off floating point exceptions for overflow, etc. */ _control87(MCW_EM, MCW_EM); #endif id_coerce = rb_intern("coerce"); id_to_i = rb_intern("to_i"); id_eq = rb_intern("=="); rb_eZeroDivError = rb_define_class("ZeroDivisionError", rb_eStandardError); rb_eFloatDomainError = rb_define_class("FloatDomainError", rb_eRangeError); rb_cNumeric = rb_define_class("Numeric", rb_cObject); rb_define_method(rb_cNumeric, "singleton_method_added", num_sadded, 1); rb_include_module(rb_cNumeric, rb_mComparable); rb_define_method(rb_cNumeric, "initialize_copy", num_init_copy, 1); rb_define_method(rb_cNumeric, "coerce", num_coerce, 1); rb_define_method(rb_cNumeric, "+@", num_uplus, 0); rb_define_method(rb_cNumeric, "-@", num_uminus, 0); rb_define_method(rb_cNumeric, "<=>", num_cmp, 1); rb_define_method(rb_cNumeric, "eql?", num_eql, 1); rb_define_method(rb_cNumeric, "quo", num_quo, 1); rb_define_method(rb_cNumeric, "div", num_div, 1); rb_define_method(rb_cNumeric, "divmod", num_divmod, 1); rb_define_method(rb_cNumeric, "modulo", num_modulo, 1); rb_define_method(rb_cNumeric, "remainder", num_remainder, 1); rb_define_method(rb_cNumeric, "abs", num_abs, 0); rb_define_method(rb_cNumeric, "to_int", num_to_int, 0); rb_define_method(rb_cNumeric, "integer?", num_int_p, 0); rb_define_method(rb_cNumeric, "zero?", num_zero_p, 0); rb_define_method(rb_cNumeric, "nonzero?", num_nonzero_p, 0); rb_define_method(rb_cNumeric, "floor", num_floor, 0); rb_define_method(rb_cNumeric, "ceil", num_ceil, 0); rb_define_method(rb_cNumeric, "round", num_round, 0); rb_define_method(rb_cNumeric, "truncate", num_truncate, 0); rb_define_method(rb_cNumeric, "step", num_step, -1); rb_cInteger = rb_define_class("Integer", rb_cNumeric); rb_undef_alloc_func(rb_cInteger); rb_undef_method(CLASS_OF(rb_cInteger), "new"); rb_define_method(rb_cInteger, "integer?", int_int_p, 0); rb_define_method(rb_cInteger, "upto", int_upto, 1); rb_define_method(rb_cInteger, "downto", int_downto, 1); rb_define_method(rb_cInteger, "times", int_dotimes, 0); rb_include_module(rb_cInteger, rb_mPrecision); rb_define_method(rb_cInteger, "succ", int_succ, 0); rb_define_method(rb_cInteger, "next", int_succ, 0); rb_define_method(rb_cInteger, "chr", int_chr, 0); rb_define_method(rb_cInteger, "to_i", int_to_i, 0); rb_define_method(rb_cInteger, "to_int", int_to_i, 0); rb_define_method(rb_cInteger, "floor", int_to_i, 0); rb_define_method(rb_cInteger, "ceil", int_to_i, 0); rb_define_method(rb_cInteger, "round", int_to_i, 0); rb_define_method(rb_cInteger, "truncate", int_to_i, 0); rb_cFixnum = rb_define_class("Fixnum", rb_cInteger); rb_include_module(rb_cFixnum, rb_mPrecision); rb_define_singleton_method(rb_cFixnum, "induced_from", rb_fix_induced_from, 1); rb_define_singleton_method(rb_cInteger, "induced_from", rb_int_induced_from, 1); rb_define_method(rb_cFixnum, "to_s", fix_to_s, -1); rb_define_method(rb_cFixnum, "id2name", fix_id2name, 0); rb_define_method(rb_cFixnum, "to_sym", fix_to_sym, 0); rb_define_method(rb_cFixnum, "-@", fix_uminus, 0); rb_define_method(rb_cFixnum, "+", fix_plus, 1); rb_define_method(rb_cFixnum, "-", fix_minus, 1); rb_define_method(rb_cFixnum, "*", fix_mul, 1); rb_define_method(rb_cFixnum, "/", fix_div, 1); rb_define_method(rb_cFixnum, "div", fix_div, 1); rb_define_method(rb_cFixnum, "%", fix_mod, 1); rb_define_method(rb_cFixnum, "modulo", fix_mod, 1); rb_define_method(rb_cFixnum, "divmod", fix_divmod, 1); rb_define_method(rb_cFixnum, "quo", fix_quo, 1); rb_define_method(rb_cFixnum, "**", fix_pow, 1); rb_define_method(rb_cFixnum, "abs", fix_abs, 0); rb_define_method(rb_cFixnum, "==", fix_equal, 1); rb_define_method(rb_cFixnum, "<=>", fix_cmp, 1); rb_define_method(rb_cFixnum, ">", fix_gt, 1); rb_define_method(rb_cFixnum, ">=", fix_ge, 1); rb_define_method(rb_cFixnum, "<", fix_lt, 1); rb_define_method(rb_cFixnum, "<=", fix_le, 1); rb_define_method(rb_cFixnum, "~", fix_rev, 0); rb_define_method(rb_cFixnum, "&", fix_and, 1); rb_define_method(rb_cFixnum, "|", fix_or, 1); rb_define_method(rb_cFixnum, "^", fix_xor, 1); rb_define_method(rb_cFixnum, "[]", fix_aref, 1); rb_define_method(rb_cFixnum, "<<", rb_fix_lshift, 1); rb_define_method(rb_cFixnum, ">>", rb_fix_rshift, 1); rb_define_method(rb_cFixnum, "to_f", fix_to_f, 0); rb_define_method(rb_cFixnum, "size", fix_size, 0); rb_define_method(rb_cFixnum, "zero?", fix_zero_p, 0); rb_cFloat = rb_define_class("Float", rb_cNumeric); rb_undef_alloc_func(rb_cFloat); rb_undef_method(CLASS_OF(rb_cFloat), "new"); rb_define_singleton_method(rb_cFloat, "induced_from", rb_flo_induced_from, 1); rb_include_module(rb_cFloat, rb_mPrecision); rb_define_const(rb_cFloat, "ROUNDS", INT2FIX(FLT_ROUNDS)); rb_define_const(rb_cFloat, "RADIX", INT2FIX(FLT_RADIX)); rb_define_const(rb_cFloat, "MANT_DIG", INT2FIX(DBL_MANT_DIG)); rb_define_const(rb_cFloat, "DIG", INT2FIX(DBL_DIG)); rb_define_const(rb_cFloat, "MIN_EXP", INT2FIX(DBL_MIN_EXP)); rb_define_const(rb_cFloat, "MAX_EXP", INT2FIX(DBL_MAX_EXP)); rb_define_const(rb_cFloat, "MIN_10_EXP", INT2FIX(DBL_MIN_10_EXP)); rb_define_const(rb_cFloat, "MAX_10_EXP", INT2FIX(DBL_MAX_10_EXP)); rb_define_const(rb_cFloat, "MIN", rb_float_new(DBL_MIN)); rb_define_const(rb_cFloat, "MAX", rb_float_new(DBL_MAX)); rb_define_const(rb_cFloat, "EPSILON", rb_float_new(DBL_EPSILON)); rb_define_method(rb_cFloat, "to_s", flo_to_s, 0); rb_define_method(rb_cFloat, "coerce", flo_coerce, 1); rb_define_method(rb_cFloat, "-@", flo_uminus, 0); rb_define_method(rb_cFloat, "+", flo_plus, 1); rb_define_method(rb_cFloat, "-", flo_minus, 1); rb_define_method(rb_cFloat, "*", flo_mul, 1); rb_define_method(rb_cFloat, "/", flo_div, 1); rb_define_method(rb_cFloat, "%", flo_mod, 1); rb_define_method(rb_cFloat, "modulo", flo_mod, 1); rb_define_method(rb_cFloat, "divmod", flo_divmod, 1); rb_define_method(rb_cFloat, "**", flo_pow, 1); rb_define_method(rb_cFloat, "==", flo_eq, 1); rb_define_method(rb_cFloat, "<=>", flo_cmp, 1); rb_define_method(rb_cFloat, ">", flo_gt, 1); rb_define_method(rb_cFloat, ">=", flo_ge, 1); rb_define_method(rb_cFloat, "<", flo_lt, 1); rb_define_method(rb_cFloat, "<=", flo_le, 1); rb_define_method(rb_cFloat, "eql?", flo_eql, 1); rb_define_method(rb_cFloat, "hash", flo_hash, 0); rb_define_method(rb_cFloat, "to_f", flo_to_f, 0); rb_define_method(rb_cFloat, "abs", flo_abs, 0); rb_define_method(rb_cFloat, "zero?", flo_zero_p, 0); rb_define_method(rb_cFloat, "to_i", flo_truncate, 0); rb_define_method(rb_cFloat, "to_int", flo_truncate, 0); rb_define_method(rb_cFloat, "floor", flo_floor, 0); rb_define_method(rb_cFloat, "ceil", flo_ceil, 0); rb_define_method(rb_cFloat, "round", flo_round, 0); rb_define_method(rb_cFloat, "truncate", flo_truncate, 0); rb_define_method(rb_cFloat, "nan?", flo_is_nan_p, 0); rb_define_method(rb_cFloat, "infinite?", flo_is_infinite_p, 0); rb_define_method(rb_cFloat, "finite?", flo_is_finite_p, 0); } /********************************************************************** object.c - $Author: shyouhei $ $Date: 2008-06-18 08:21:30 +0200 (Wed, 18 Jun 2008) $ created at: Thu Jul 15 12:01:24 JST 1993 Copyright (C) 1993-2003 Yukihiro Matsumoto Copyright (C) 2000 Network Applied Communication Laboratory, Inc. Copyright (C) 2000 Information-technology Promotion Agency, Japan **********************************************************************/ #include "ruby.h" #include "st.h" #include "util.h" #include #include #include #include VALUE rb_mKernel; VALUE rb_cObject; VALUE rb_cModule; VALUE rb_cClass; VALUE rb_cData; VALUE rb_cNilClass; VALUE rb_cTrueClass; VALUE rb_cFalseClass; VALUE rb_cSymbol; static ID id_eq, id_eql, id_inspect, id_init_copy; /* * call-seq: * obj === other => true or false * * Case Equality---For class Object, effectively the same * as calling #==, but typically overridden by descendents * to provide meaningful semantics in case statements. */ VALUE rb_equal(obj1, obj2) VALUE obj1, obj2; { VALUE result; if (obj1 == obj2) return Qtrue; result = rb_funcall(obj1, id_eq, 1, obj2); if (RTEST(result)) return Qtrue; return Qfalse; } int rb_eql(obj1, obj2) VALUE obj1, obj2; { return RTEST(rb_funcall(obj1, id_eql, 1, obj2)); } /* * call-seq: * obj == other => true or false * obj.equal?(other) => true or false * obj.eql?(other) => true or false * * Equality---At the Object level, == returns * true only if obj and other are the * same object. Typically, this method is overridden in descendent * classes to provide class-specific meaning. * * Unlike ==, the equal? method should never be * overridden by subclasses: it is used to determine object identity * (that is, a.equal?(b) iff a is the same * object as b). * * The eql? method returns true if obj and anObject have the * same value. Used by Hash to test members for equality. * For objects of class Object, eql? is * synonymous with ==. Subclasses normally continue this * tradition, but there are exceptions. Numeric types, for * example, perform type conversion across ==, but not * across eql?, so: * * 1 == 1.0 #=> true * 1.eql? 1.0 #=> false */ static VALUE rb_obj_equal(obj1, obj2) VALUE obj1, obj2; { if (obj1 == obj2) return Qtrue; return Qfalse; } /* * call-seq: * obj.id => fixnum * * Soon-to-be deprecated version of Object#object_id. */ VALUE rb_obj_id_obsolete(obj) VALUE obj; { rb_warn("Object#id will be deprecated; use Object#object_id"); return rb_obj_id(obj); } VALUE rb_class_real(cl) VALUE cl; { while (FL_TEST(cl, FL_SINGLETON) || TYPE(cl) == T_ICLASS) { cl = RCLASS(cl)->super; } return cl; } /* * call-seq: * obj.type => class * * Deprecated synonym for Object#class. */ VALUE rb_obj_type(obj) VALUE obj; { rb_warn("Object#type is deprecated; use Object#class"); return rb_class_real(CLASS_OF(obj)); } /* * call-seq: * obj.class => class * * Returns the class of obj, now preferred over * Object#type, as an object's type in Ruby is only * loosely tied to that object's class. This method must always be * called with an explicit receiver, as class is also a * reserved word in Ruby. * * 1.class #=> Fixnum * self.class #=> Object */ VALUE rb_obj_class(obj) VALUE obj; { return rb_class_real(CLASS_OF(obj)); } static void init_copy(dest, obj) VALUE dest, obj; { if (OBJ_FROZEN(dest)) { rb_raise(rb_eTypeError, "[bug] frozen object (%s) allocated", rb_obj_classname(dest)); } RBASIC(dest)->flags &= ~(T_MASK|FL_EXIVAR); RBASIC(dest)->flags |= RBASIC(obj)->flags & (T_MASK|FL_EXIVAR|FL_TAINT); if (FL_TEST(obj, FL_EXIVAR)) { rb_copy_generic_ivar(dest, obj); } rb_gc_copy_finalizer(dest, obj); switch (TYPE(obj)) { case T_OBJECT: case T_CLASS: case T_MODULE: if (ROBJECT(dest)->iv_tbl) { st_free_table(ROBJECT(dest)->iv_tbl); ROBJECT(dest)->iv_tbl = 0; } if (ROBJECT(obj)->iv_tbl) { ROBJECT(dest)->iv_tbl = st_copy(ROBJECT(obj)->iv_tbl); } } rb_funcall(dest, id_init_copy, 1, obj); } /* * call-seq: * obj.clone -> an_object * * Produces a shallow copy of obj---the instance variables of * obj are copied, but not the objects they reference. Copies * the frozen and tainted state of obj. See also the discussion * under Object#dup. * * class Klass * attr_accessor :str * end * s1 = Klass.new #=> # * s1.str = "Hello" #=> "Hello" * s2 = s1.clone #=> # * s2.str[1,4] = "i" #=> "i" * s1.inspect #=> "#" * s2.inspect #=> "#" * * This method may have class-specific behavior. If so, that * behavior will be documented under the #+initialize_copy+ method of * the class. */ VALUE rb_obj_clone(obj) VALUE obj; { VALUE clone; if (rb_special_const_p(obj)) { rb_raise(rb_eTypeError, "can't clone %s", rb_obj_classname(obj)); } clone = rb_obj_alloc(rb_obj_class(obj)); RBASIC(clone)->klass = rb_singleton_class_clone(obj); RBASIC(clone)->flags = (RBASIC(obj)->flags | FL_TEST(clone, FL_TAINT)) & ~(FL_FREEZE|FL_FINALIZE); init_copy(clone, obj); RBASIC(clone)->flags |= RBASIC(obj)->flags & FL_FREEZE; return clone; } /* * call-seq: * obj.dup -> an_object * * Produces a shallow copy of obj---the instance variables of * obj are copied, but not the objects they reference. * dup copies the tainted state of obj. See also * the discussion under Object#clone. In general, * clone and dup may have different semantics * in descendent classes. While clone is used to duplicate * an object, including its internal state, dup typically * uses the class of the descendent object to create the new instance. * * This method may have class-specific behavior. If so, that * behavior will be documented under the #+initialize_copy+ method of * the class. */ VALUE rb_obj_dup(obj) VALUE obj; { VALUE dup; if (rb_special_const_p(obj)) { rb_raise(rb_eTypeError, "can't dup %s", rb_obj_classname(obj)); } dup = rb_obj_alloc(rb_obj_class(obj)); init_copy(dup, obj); return dup; } /* :nodoc: */ VALUE rb_obj_init_copy(obj, orig) VALUE obj, orig; { if (obj == orig) return obj; rb_check_frozen(obj); if (TYPE(obj) != TYPE(orig) || rb_obj_class(obj) != rb_obj_class(orig)) { rb_raise(rb_eTypeError, "initialize_copy should take same class object"); } return obj; } /* * call-seq: * obj.to_a -> anArray * * Returns an array representation of obj. For objects of class * Object and others that don't explicitly override the * method, the return value is an array containing self. * However, this latter behavior will soon be obsolete. * * self.to_a #=> -:1: warning: default `to_a' will be obsolete * "hello".to_a #=> ["hello"] * Time.new.to_a #=> [39, 54, 8, 9, 4, 2003, 3, 99, true, "CDT"] */ static VALUE rb_any_to_a(obj) VALUE obj; { rb_warn("default `to_a' will be obsolete"); return rb_ary_new3(1, obj); } /* * call-seq: * obj.to_s => string * * Returns a string representing obj. The default * to_s prints the object's class and an encoding of the * object id. As a special case, the top-level object that is the * initial execution context of Ruby programs returns ``main.'' */ VALUE rb_any_to_s(obj) VALUE obj; { char *cname = rb_obj_classname(obj); size_t len; VALUE str; len = strlen(cname)+6+16; str = rb_str_new(0, len); /* 6:tags 16:addr */ snprintf(RSTRING(str)->ptr, len+1, "#<%s:0x%lx>", cname, obj); RSTRING(str)->len = strlen(RSTRING(str)->ptr); if (OBJ_TAINTED(obj)) OBJ_TAINT(str); return str; } VALUE rb_inspect(obj) VALUE obj; { return rb_obj_as_string(rb_funcall(obj, id_inspect, 0, 0)); } static int inspect_i(id, value, str) ID id; VALUE value; VALUE str; { VALUE str2; char *ivname; /* need not to show internal data */ if (CLASS_OF(value) == 0) return ST_CONTINUE; if (!rb_is_instance_id(id)) return ST_CONTINUE; if (RSTRING(str)->ptr[0] == '-') { /* first element */ RSTRING(str)->ptr[0] = '#'; rb_str_cat2(str, " "); } else { rb_str_cat2(str, ", "); } ivname = rb_id2name(id); rb_str_cat2(str, ivname); rb_str_cat2(str, "="); str2 = rb_inspect(value); rb_str_append(str, str2); OBJ_INFECT(str, str2); return ST_CONTINUE; } static VALUE inspect_obj(obj, str) VALUE obj, str; { st_foreach_safe(ROBJECT(obj)->iv_tbl, inspect_i, str); rb_str_cat2(str, ">"); RSTRING(str)->ptr[0] = '#'; OBJ_INFECT(str, obj); return str; } /* * call-seq: * obj.inspect => string * * Returns a string containing a human-readable representation of * obj. If not overridden, uses the to_s method to * generate the string. * * [ 1, 2, 3..4, 'five' ].inspect #=> "[1, 2, 3..4, \"five\"]" * Time.new.inspect #=> "Wed Apr 09 08:54:39 CDT 2003" */ static VALUE rb_obj_inspect(obj) VALUE obj; { if (TYPE(obj) == T_OBJECT && ROBJECT(obj)->iv_tbl && ROBJECT(obj)->iv_tbl->num_entries > 0) { VALUE str; size_t len; char *c; c = rb_obj_classname(obj); if (rb_inspecting_p(obj)) { len = strlen(c)+10+16+1; str = rb_str_new(0, len); /* 10:tags 16:addr 1:nul */ snprintf(RSTRING(str)->ptr, len, "#<%s:0x%lx ...>", c, obj); RSTRING(str)->len = strlen(RSTRING(str)->ptr); return str; } len = strlen(c)+6+16+1; str = rb_str_new(0, len); /* 6:tags 16:addr 1:nul */ snprintf(RSTRING(str)->ptr, len, "-<%s:0x%lx", c, obj); RSTRING(str)->len = strlen(RSTRING(str)->ptr); return rb_protect_inspect(inspect_obj, obj, str); } return rb_funcall(obj, rb_intern("to_s"), 0, 0); } /* * call-seq: * obj.instance_of?(class) => true or false * * Returns true if obj is an instance of the given * class. See also Object#kind_of?. */ VALUE rb_obj_is_instance_of(obj, c) VALUE obj, c; { switch (TYPE(c)) { case T_MODULE: case T_CLASS: case T_ICLASS: break; default: rb_raise(rb_eTypeError, "class or module required"); } if (rb_obj_class(obj) == c) return Qtrue; return Qfalse; } /* * call-seq: * obj.is_a?(class) => true or false * obj.kind_of?(class) => true or false * * Returns true if class is the class of * obj, or if class is one of the superclasses of * obj or modules included in obj. * * module M; end * class A * include M * end * class B < A; end * class C < B; end * b = B.new * b.instance_of? A #=> false * b.instance_of? B #=> true * b.instance_of? C #=> false * b.instance_of? M #=> false * b.kind_of? A #=> true * b.kind_of? B #=> true * b.kind_of? C #=> false * b.kind_of? M #=> true */ VALUE rb_obj_is_kind_of(obj, c) VALUE obj, c; { VALUE cl = CLASS_OF(obj); switch (TYPE(c)) { case T_MODULE: case T_CLASS: case T_ICLASS: break; default: rb_raise(rb_eTypeError, "class or module required"); } while (cl) { if (cl == c || RCLASS(cl)->m_tbl == RCLASS(c)->m_tbl) return Qtrue; cl = RCLASS(cl)->super; } return Qfalse; } /* * Document-method: inherited * * call-seq: * inherited(subclass) * * Callback invoked whenever a subclass of the current class is created. * * Example: * * class Foo * def self.inherited(subclass) * puts "New subclass: #{subclass}" * end * end * * class Bar < Foo * end * * class Baz < Bar * end * * produces: * * New subclass: Bar * New subclass: Baz */ /* * Document-method: singleton_method_added * * call-seq: * singleton_method_added(symbol) * * Invoked as a callback whenever a singleton method is added to the * receiver. * * module Chatty * def Chatty.singleton_method_added(id) * puts "Adding #{id.id2name}" * end * def self.one() end * def two() end * def Chatty.three() end * end * * produces: * * Adding singleton_method_added * Adding one * Adding three * */ /* * Document-method: singleton_method_removed * * call-seq: * singleton_method_removed(symbol) * * Invoked as a callback whenever a singleton method is removed from * the receiver. * * module Chatty * def Chatty.singleton_method_removed(id) * puts "Removing #{id.id2name}" * end * def self.one() end * def two() end * def Chatty.three() end * class <produces: * * Removing three * Removing one */ /* * Document-method: singleton_method_undefined * * call-seq: * singleton_method_undefined(symbol) * * Invoked as a callback whenever a singleton method is undefined in * the receiver. * * module Chatty * def Chatty.singleton_method_undefined(id) * puts "Undefining #{id.id2name}" * end * def Chatty.one() end * class << self * undef_method(:one) * end * end * * produces: * * Undefining one */ /* * Document-method: included * * call-seq: * included( othermod ) * * Callback invoked whenever the receiver is included in another * module or class. This should be used in preference to * Module.append_features if your code wants to perform some * action when a module is included in another. * * module A * def A.included(mod) * puts "#{self} included in #{mod}" * end * end * module Enumerable * include A * end */ /* * Not documented */ static VALUE rb_obj_dummy() { return Qnil; } /* * call-seq: * obj.tainted? => true or false * * Returns true if the object is tainted. */ VALUE rb_obj_tainted(obj) VALUE obj; { if (OBJ_TAINTED(obj)) return Qtrue; return Qfalse; } /* * call-seq: * obj.taint -> obj * * Marks obj as tainted---if the $SAFE level is * set appropriately, many method calls which might alter the running * programs environment will refuse to accept tainted strings. */ VALUE rb_obj_taint(obj) VALUE obj; { rb_secure(4); if (!OBJ_TAINTED(obj)) { if (OBJ_FROZEN(obj)) { rb_error_frozen("object"); } OBJ_TAINT(obj); } return obj; } /* * call-seq: * obj.untaint => obj * * Removes the taint from obj. */ VALUE rb_obj_untaint(obj) VALUE obj; { rb_secure(3); if (OBJ_TAINTED(obj)) { if (OBJ_FROZEN(obj)) { rb_error_frozen("object"); } FL_UNSET(obj, FL_TAINT); } return obj; } void rb_obj_infect(obj1, obj2) VALUE obj1, obj2; { OBJ_INFECT(obj1, obj2); } /* * call-seq: * obj.freeze => obj * * Prevents further modifications to obj. A * TypeError will be raised if modification is attempted. * There is no way to unfreeze a frozen object. See also * Object#frozen?. * * a = [ "a", "b", "c" ] * a.freeze * a << "z" * * produces: * * prog.rb:3:in `<<': can't modify frozen array (TypeError) * from prog.rb:3 */ VALUE rb_obj_freeze(obj) VALUE obj; { if (!OBJ_FROZEN(obj)) { if (rb_safe_level() >= 4 && !OBJ_TAINTED(obj)) { rb_raise(rb_eSecurityError, "Insecure: can't freeze object"); } OBJ_FREEZE(obj); } return obj; } /* * call-seq: * obj.frozen? => true or false * * Returns the freeze status of obj. * * a = [ "a", "b", "c" ] * a.freeze #=> ["a", "b", "c"] * a.frozen? #=> true */ static VALUE rb_obj_frozen_p(obj) VALUE obj; { if (OBJ_FROZEN(obj)) return Qtrue; return Qfalse; } /* * Document-class: NilClass * * The class of the singleton object nil. */ /* * call-seq: * nil.to_i => 0 * * Always returns zero. * * nil.to_i #=> 0 */ static VALUE nil_to_i(obj) VALUE obj; { return INT2FIX(0); } /* * call-seq: * nil.to_f => 0.0 * * Always returns zero. * * nil.to_f #=> 0.0 */ static VALUE nil_to_f(obj) VALUE obj; { return rb_float_new(0.0); } /* * call-seq: * nil.to_s => "" * * Always returns the empty string. * * nil.to_s #=> "" */ static VALUE nil_to_s(obj) VALUE obj; { return rb_str_new2(""); } /* * call-seq: * nil.to_a => [] * * Always returns an empty array. * * nil.to_a #=> [] */ static VALUE nil_to_a(obj) VALUE obj; { return rb_ary_new2(0); } /* * call-seq: * nil.inspect => "nil" * * Always returns the string "nil". */ static VALUE nil_inspect(obj) VALUE obj; { return rb_str_new2("nil"); } static VALUE main_to_s(obj) VALUE obj; { return rb_str_new2("main"); } /*********************************************************************** * Document-class: TrueClass * * The global value true is the only instance of class * TrueClass and represents a logically true value in * boolean expressions. The class provides operators allowing * true to be used in logical expressions. */ /* * call-seq: * true.to_s => "true" * * The string representation of true is "true". */ static VALUE true_to_s(obj) VALUE obj; { return rb_str_new2("true"); } /* * call-seq: * true & obj => true or false * * And---Returns false if obj is * nil or false, true otherwise. */ static VALUE true_and(obj, obj2) VALUE obj, obj2; { return RTEST(obj2)?Qtrue:Qfalse; } /* * call-seq: * true | obj => true * * Or---Returns true. As anObject is an argument to * a method call, it is always evaluated; there is no short-circuit * evaluation in this case. * * true | puts("or") * true || puts("logical or") * * produces: * * or */ static VALUE true_or(obj, obj2) VALUE obj, obj2; { return Qtrue; } /* * call-seq: * true ^ obj => !obj * * Exclusive Or---Returns true if obj is * nil or false, false * otherwise. */ static VALUE true_xor(obj, obj2) VALUE obj, obj2; { return RTEST(obj2)?Qfalse:Qtrue; } /* * Document-class: FalseClass * * The global value false is the only instance of class * FalseClass and represents a logically false value in * boolean expressions. The class provides operators allowing * false to participate correctly in logical expressions. * */ /* * call-seq: * false.to_s => "false" * * 'nuf said... */ static VALUE false_to_s(obj) VALUE obj; { return rb_str_new2("false"); } /* * call-seq: * false & obj => false * nil & obj => false * * And---Returns false. obj is always * evaluated as it is the argument to a method call---there is no * short-circuit evaluation in this case. */ static VALUE false_and(obj, obj2) VALUE obj, obj2; { return Qfalse; } /* * call-seq: * false | obj => true or false * nil | obj => true or false * * Or---Returns false if obj is * nil or false; true otherwise. */ static VALUE false_or(obj, obj2) VALUE obj, obj2; { return RTEST(obj2)?Qtrue:Qfalse; } /* * call-seq: * false ^ obj => true or false * nil ^ obj => true or false * * Exclusive Or---If obj is nil or * false, returns false; otherwise, returns * true. * */ static VALUE false_xor(obj, obj2) VALUE obj, obj2; { return RTEST(obj2)?Qtrue:Qfalse; } /* * call_seq: * nil.nil? => true * * Only the object nil responds true to nil?. */ static VALUE rb_true(obj) VALUE obj; { return Qtrue; } /* * call_seq: * nil.nil? => true * .nil? => false * * Only the object nil responds true to nil?. */ static VALUE rb_false(obj) VALUE obj; { return Qfalse; } /* * call-seq: * obj =~ other => false * * Pattern Match---Overridden by descendents (notably * Regexp and String) to provide meaningful * pattern-match semantics. */ static VALUE rb_obj_pattern_match(obj1, obj2) VALUE obj1, obj2; { return 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.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(sym) VALUE sym; { ID id = SYM2ID(sym); return LONG2FIX(id); } /* :nodoc: */ static VALUE sym_to_int(sym) VALUE sym; { rb_warning("treating Symbol as an integer"); return sym_to_i(sym); } /* * call-seq: * sym.inspect => string * * Returns the representation of sym as a symbol literal. * * :fred.inspect #=> ":fred" */ static VALUE sym_inspect(sym) VALUE sym; { VALUE str; char *name; ID id = SYM2ID(sym); name = rb_id2name(id); str = rb_str_new(0, strlen(name)+1); RSTRING(str)->ptr[0] = ':'; strcpy(RSTRING(str)->ptr+1, name); if (!rb_symname_p(name)) { str = rb_str_dump(str); strncpy(RSTRING(str)->ptr, ":\"", 2); } return str; } /* * call-seq: * sym.id2name => string * sym.to_s => string * * Returns the name or string corresponding to sym. * * :fred.id2name #=> "fred" */ static VALUE sym_to_s(sym) VALUE sym; { return rb_str_new2(rb_id2name(SYM2ID(sym))); } /* * call-seq: * sym.to_sym => 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(sym) VALUE sym; { return sym; } /*********************************************************************** * * Document-class: Module * * A Module is a collection of methods and constants. The * methods in a module may be instance methods or module methods. * Instance methods appear as methods in a class when the module is * included, module methods do not. Conversely, module methods may be * called without creating an encapsulating object, while instance * methods may not. (See Module#module_function) * * In the descriptions that follow, the parameter syml refers * to a symbol, which is either a quoted string or a * Symbol (such as :name). * * module Mod * include Math * CONST = 1 * def meth * # ... * end * end * Mod.class #=> Module * Mod.constants #=> ["E", "PI", "CONST"] * Mod.instance_methods #=> ["meth"] * */ /* * call-seq: * mod.to_s => string * * Return a string representing this module or class. For basic * classes and modules, this is the name. For singletons, we * show information on the thing we're attached to as well. */ static VALUE rb_mod_to_s(klass) VALUE klass; { if (FL_TEST(klass, FL_SINGLETON)) { VALUE s = rb_str_new2("#<"); VALUE v = rb_iv_get(klass, "__attached__"); rb_str_cat2(s, "Class:"); switch (TYPE(v)) { case T_CLASS: case T_MODULE: rb_str_append(s, rb_inspect(v)); break; default: rb_str_append(s, rb_any_to_s(v)); break; } rb_str_cat2(s, ">"); return s; } return rb_str_dup(rb_class_name(klass)); } /* * call-seq: * mod.freeze * * Prevents further modifications to mod. */ static VALUE rb_mod_freeze(mod) VALUE mod; { rb_mod_to_s(mod); return rb_obj_freeze(mod); } /* * call-seq: * mod === obj => true or false * * Case Equality---Returns true if anObject is an * instance of mod or one of mod's descendents. Of * limited use for modules, but can be used in case * statements to classify objects by class. */ static VALUE rb_mod_eqq(mod, arg) VALUE mod, arg; { return rb_obj_is_kind_of(arg, mod); } /* * call-seq: * mod <= other => true, false, or nil * * Returns true if mod is a subclass of other or * is the same as other. Returns * nil if there's no relationship between the two. * (Think of the relationship in terms of the class definition: * "class Am_tbl == RCLASS(arg)->m_tbl) return Qtrue; mod = RBASIC(mod)->klass; } while (mod) { if (RCLASS(mod)->m_tbl == RCLASS(arg)->m_tbl) return Qtrue; mod = RCLASS(mod)->super; } /* not mod < arg; check if mod > arg */ while (arg) { if (RCLASS(arg)->m_tbl == RCLASS(start)->m_tbl) return Qfalse; arg = RCLASS(arg)->super; } return Qnil; } /* * call-seq: * mod < other => true, false, or nil * * Returns true if mod is a subclass of other. Returns * nil if there's no relationship between the two. * (Think of the relationship in terms of the class definition: * "class A= other => true, false, or nil * * Returns true if mod is an ancestor of other, or the * two modules are the same. Returns * nil if there's no relationship between the two. * (Think of the relationship in terms of the class definition: * "class AA"). * */ static VALUE rb_mod_ge(mod, arg) VALUE mod, arg; { switch (TYPE(arg)) { case T_MODULE: case T_CLASS: break; default: rb_raise(rb_eTypeError, "compared with non class/module"); } return rb_class_inherited_p(arg, mod); } /* * call-seq: * mod > other => true, false, or nil * * Returns true if mod is an ancestor of other. Returns * nil if there's no relationship between the two. * (Think of the relationship in terms of the class definition: * "class AA"). * */ static VALUE rb_mod_gt(mod, arg) VALUE mod, arg; { if (mod == arg) return Qfalse; return rb_mod_ge(mod, arg); } /* * call-seq: * mod <=> other_mod => -1, 0, +1, or nil * * Comparison---Returns -1 if mod includes other_mod, 0 if * mod is the same as other_mod, and +1 if mod is * included by other_mod or if mod has no relationship with * other_mod. Returns nil if other_mod is * not a module. */ static VALUE rb_mod_cmp(mod, arg) VALUE mod, arg; { VALUE cmp; if (mod == arg) return INT2FIX(0); switch (TYPE(arg)) { case T_MODULE: case T_CLASS: break; default: return Qnil; } cmp = rb_class_inherited_p(mod, arg); if (NIL_P(cmp)) return Qnil; if (cmp) { return INT2FIX(-1); } return INT2FIX(1); } static VALUE rb_module_s_alloc _((VALUE)); static VALUE rb_module_s_alloc(klass) VALUE klass; { VALUE mod = rb_module_new(); RBASIC(mod)->klass = klass; return mod; } static VALUE rb_class_s_alloc _((VALUE)); static VALUE rb_class_s_alloc(klass) VALUE klass; { return rb_class_boot(0); } /* * call-seq: * Module.new => mod * Module.new {|mod| block } => mod * * Creates a new anonymous module. If a block is given, it is passed * the module object, and the block is evaluated in the context of this * module using module_eval. * * Fred = Module.new do * def meth1 * "hello" * end * def meth2 * "bye" * end * end * a = "my string" * a.extend(Fred) #=> "my string" * a.meth1 #=> "hello" * a.meth2 #=> "bye" */ static VALUE rb_mod_initialize(module) VALUE module; { if (rb_block_given_p()) { rb_mod_module_eval(0, 0, module); } return Qnil; } /* * call-seq: * Class.new(super_class=Object) => a_class * * Creates a new anonymous (unnamed) class with the given superclass * (or Object if no parameter is given). You can give a * class a name by assigning the class object to a constant. * */ static VALUE rb_class_initialize(argc, argv, klass) int argc; VALUE *argv; VALUE klass; { VALUE super; if (RCLASS(klass)->super != 0) { rb_raise(rb_eTypeError, "already initialized class"); } if (rb_scan_args(argc, argv, "01", &super) == 0) { super = rb_cObject; } else { rb_check_inheritable(super); } RCLASS(klass)->super = super; rb_make_metaclass(klass, RBASIC(super)->klass); rb_mod_initialize(klass); rb_class_inherited(super, klass); return klass; } /* * call-seq: * class.allocate() => obj * * Allocates space for a new object of class's class. The * returned object must be an instance of class. * */ VALUE rb_obj_alloc(klass) VALUE klass; { VALUE obj; if (RCLASS(klass)->super == 0) { rb_raise(rb_eTypeError, "can't instantiate uninitialized class"); } if (FL_TEST(klass, FL_SINGLETON)) { rb_raise(rb_eTypeError, "can't create instance of virtual class"); } obj = rb_funcall(klass, ID_ALLOCATOR, 0, 0); if (rb_obj_class(obj) != rb_class_real(klass)) { rb_raise(rb_eTypeError, "wrong instance allocation"); } return obj; } static VALUE rb_class_allocate_instance _((VALUE)); static VALUE rb_class_allocate_instance(klass) VALUE klass; { NEWOBJ(obj, struct RObject); OBJSETUP(obj, klass, T_OBJECT); return (VALUE)obj; } /* * call-seq: * class.new(args, ...) => obj * * Calls allocate to create a new object of * class's class, then invokes that object's * initialize method, passing it args. * This is the method that ends up getting called whenever * an object is constructed using .new. * */ VALUE rb_class_new_instance(argc, argv, klass) int argc; VALUE *argv; VALUE klass; { VALUE obj; obj = rb_obj_alloc(klass); rb_obj_call_init(obj, argc, argv); return obj; } /* * call-seq: * class.superclass -> a_super_class or nil * * Returns the superclass of class, or nil. * * File.superclass #=> IO * IO.superclass #=> Object * Object.superclass #=> nil * */ static VALUE rb_class_superclass(klass) VALUE klass; { VALUE super = RCLASS(klass)->super; if (!super) { rb_raise(rb_eTypeError, "uninitialized class"); } if (FL_TEST(klass, FL_SINGLETON)) { super = RBASIC(klass)->klass; } while (TYPE(super) == T_ICLASS) { super = RCLASS(super)->super; } if (!super) { return Qnil; } return super; } static ID str_to_id(str) VALUE str; { VALUE sym = rb_str_intern(str); return SYM2ID(sym); } ID rb_to_id(name) VALUE name; { VALUE tmp; ID id; switch (TYPE(name)) { case T_STRING: return str_to_id(name); case T_FIXNUM: rb_warn("do not use Fixnums as Symbols"); id = FIX2LONG(name); if (!rb_id2name(id)) { rb_raise(rb_eArgError, "%ld is not a symbol", id); } break; case T_SYMBOL: id = SYM2ID(name); break; default: tmp = rb_check_string_type(name); if (!NIL_P(tmp)) { return str_to_id(tmp); } rb_raise(rb_eTypeError, "%s is not a symbol", RSTRING(rb_inspect(name))->ptr); } return id; } /* * call-seq: * attr(symbol, writable=false) => nil * * Defines a named attribute for this module, where the name is * symbol.id2name, creating an instance variable * (@name) and a corresponding access method to read it. * If the optional writable argument is true, also * creates a method called name= to set the attribute. * * module Mod * attr :size, true * end * * is equivalent to: * * module Mod * def size * @size * end * def size=(val) * @size = val * end * end */ static VALUE rb_mod_attr(argc, argv, klass) int argc; VALUE *argv; VALUE klass; { VALUE name, pub; rb_scan_args(argc, argv, "11", &name, &pub); rb_attr(klass, rb_to_id(name), 1, RTEST(pub), Qtrue); return Qnil; } /* * call-seq: * attr_reader(symbol, ...) => nil * * Creates instance variables and corresponding methods that return the * value of each instance variable. Equivalent to calling * ``attr:name'' on each name in turn. */ static VALUE rb_mod_attr_reader(argc, argv, klass) int argc; VALUE *argv; VALUE klass; { int i; for (i=0; i nil * * Creates an accessor method to allow assignment to the attribute * aSymbol.id2name. */ static VALUE rb_mod_attr_writer(argc, argv, klass) int argc; VALUE *argv; VALUE klass; { int i; for (i=0; i nil * * Equivalent to calling ``attrsymbol, * true'' on each symbol in turn. * * module Mod * attr_accessor(:one, :two) * end * Mod.instance_methods.sort #=> ["one", "one=", "two", "two="] */ static VALUE rb_mod_attr_accessor(argc, argv, klass) int argc; VALUE *argv; VALUE klass; { int i; for (i=0; i obj * * Returns the value of the named constant in mod. * * Math.const_get(:PI) #=> 3.14159265358979 */ static VALUE rb_mod_const_get(mod, name) VALUE mod, name; { ID id = rb_to_id(name); if (!rb_is_const_id(id)) { rb_name_error(id, "wrong constant name %s", rb_id2name(id)); } return rb_const_get(mod, id); } /* * call-seq: * mod.const_set(sym, obj) => obj * * Sets the named constant to the given object, returning that object. * Creates a new constant if no constant with the given name previously * existed. * * Math.const_set("HIGH_SCHOOL_PI", 22.0/7.0) #=> 3.14285714285714 * Math::HIGH_SCHOOL_PI - Math::PI #=> 0.00126448926734968 */ static VALUE rb_mod_const_set(mod, name, value) VALUE mod, name, value; { ID id = rb_to_id(name); if (!rb_is_const_id(id)) { rb_name_error(id, "wrong constant name %s", rb_id2name(id)); } rb_const_set(mod, id, value); return value; } /* * call-seq: * mod.const_defined?(sym) => true or false * * Returns true if a constant with the given name is * defined by mod. * * Math.const_defined? "PI" #=> true */ static VALUE rb_mod_const_defined(mod, name) VALUE mod, name; { ID id = rb_to_id(name); if (!rb_is_const_id(id)) { rb_name_error(id, "wrong constant name %s", rb_id2name(id)); } return rb_const_defined_at(mod, id); } /* * call-seq: * obj.methods => array * * Returns a list of the names of methods publicly accessible in * obj. This will include all the methods accessible in * obj's ancestors. * * class Klass * def kMethod() * end * end * k = Klass.new * k.methods[0..9] #=> ["kMethod", "freeze", "nil?", "is_a?", * "class", "instance_variable_set", * "methods", "extend", "__send__", "instance_eval"] * k.methods.length #=> 42 */ static VALUE rb_obj_methods(argc, argv, obj) int argc; VALUE *argv; VALUE obj; { retry: if (argc == 0) { VALUE args[1]; args[0] = Qtrue; return rb_class_instance_methods(1, args, CLASS_OF(obj)); } else { VALUE recur; rb_scan_args(argc, argv, "1", &recur); if (RTEST(recur)) { argc = 0; goto retry; } return rb_obj_singleton_methods(argc, argv, obj); } } /* * call-seq: * obj.protected_methods(all=true) => array * * Returns the list of protected methods accessible to obj. If * the all parameter is set to false, only those methods * in the receiver will be listed. */ static VALUE rb_obj_protected_methods(argc, argv, obj) int argc; VALUE *argv; VALUE obj; { if (argc == 0) { /* hack to stop warning */ VALUE args[1]; args[0] = Qtrue; return rb_class_protected_instance_methods(1, args, CLASS_OF(obj)); } return rb_class_protected_instance_methods(argc, argv, CLASS_OF(obj)); } /* * call-seq: * obj.private_methods(all=true) => array * * Returns the list of private methods accessible to obj. If * the all parameter is set to false, only those methods * in the receiver will be listed. */ static VALUE rb_obj_private_methods(argc, argv, obj) int argc; VALUE *argv; VALUE obj; { if (argc == 0) { /* hack to stop warning */ VALUE args[1]; args[0] = Qtrue; return rb_class_private_instance_methods(1, args, CLASS_OF(obj)); } return rb_class_private_instance_methods(argc, argv, CLASS_OF(obj)); } /* * call-seq: * obj.public_methods(all=true) => array * * Returns the list of public methods accessible to obj. If * the all parameter is set to false, only those methods * in the receiver will be listed. */ static VALUE rb_obj_public_methods(argc, argv, obj) int argc; VALUE *argv; VALUE obj; { if (argc == 0) { /* hack to stop warning */ VALUE args[1]; args[0] = Qtrue; return rb_class_public_instance_methods(1, args, CLASS_OF(obj)); } return rb_class_public_instance_methods(argc, argv, CLASS_OF(obj)); } /* * call-seq: * obj.instance_variable_get(symbol) => obj * * Returns the value of the given instance variable, or nil if the * instance variable is not set. The @ part of the * variable name should be included for regular instance * variables. Throws a NameError exception if the * supplied symbol is not valid as an instance variable name. * * class Fred * def initialize(p1, p2) * @a, @b = p1, p2 * end * end * fred = Fred.new('cat', 99) * fred.instance_variable_get(:@a) #=> "cat" * fred.instance_variable_get("@b") #=> 99 */ static VALUE rb_obj_ivar_get(obj, iv) VALUE obj, iv; { ID id = rb_to_id(iv); if (!rb_is_instance_id(id)) { rb_name_error(id, "`%s' is not allowed as an instance variable name", rb_id2name(id)); } return rb_ivar_get(obj, id); } /* * call-seq: * obj.instance_variable_set(symbol, obj) => obj * * Sets the instance variable names by symbol to * object, thereby frustrating the efforts of the class's * author to attempt to provide proper encapsulation. The variable * did not have to exist prior to this call. * * class Fred * def initialize(p1, p2) * @a, @b = p1, p2 * end * end * fred = Fred.new('cat', 99) * fred.instance_variable_set(:@a, 'dog') #=> "dog" * fred.instance_variable_set(:@c, 'cat') #=> "cat" * fred.inspect #=> "#" */ static VALUE rb_obj_ivar_set(obj, iv, val) VALUE obj, iv, val; { ID id = rb_to_id(iv); if (!rb_is_instance_id(id)) { rb_name_error(id, "`%s' is not allowed as an instance variable name", rb_id2name(id)); } return rb_ivar_set(obj, id, val); } /* * call-seq: * obj.instance_variable_defined?(symbol) => true or false * * Returns true if the given instance variable is * defined in obj. * * class Fred * def initialize(p1, p2) * @a, @b = p1, p2 * end * end * fred = Fred.new('cat', 99) * fred.instance_variable_defined?(:@a) #=> true * fred.instance_variable_defined?("@b") #=> true * fred.instance_variable_defined?("@c") #=> false */ static VALUE rb_obj_ivar_defined(obj, iv) VALUE obj, iv; { ID id = rb_to_id(iv); if (!rb_is_instance_id(id)) { rb_name_error(id, "`%s' is not allowed as an instance variable name", rb_id2name(id)); } return rb_ivar_defined(obj, id); } /* * call-seq: * mod.class_variable_get(symbol) => obj * * Returns the value of the given class variable (or throws a * NameError exception). The @@ part of the * variable name should be included for regular class variables * * class Fred * @@foo = 99 * end * * def Fred.foo * class_variable_get(:@@foo) #=> 99 * end */ static VALUE rb_mod_cvar_get(obj, iv) VALUE obj, iv; { ID id = rb_to_id(iv); if (!rb_is_class_id(id)) { rb_name_error(id, "`%s' is not allowed as a class variable name", rb_id2name(id)); } return rb_cvar_get(obj, id); } /* * call-seq: * obj.class_variable_set(symbol, obj) => obj * * Sets the class variable names by symbol to * object. * * class Fred * @@foo = 99 * def foo * @@foo * end * end * * def Fred.foo * class_variable_set(:@@foo, 101) #=> 101 * end * Fred.foo * Fred.new.foo #=> 101 */ static VALUE rb_mod_cvar_set(obj, iv, val) VALUE obj, iv, val; { ID id = rb_to_id(iv); if (!rb_is_class_id(id)) { rb_name_error(id, "`%s' is not allowed as a class variable name", rb_id2name(id)); } rb_cvar_set(obj, id, val, Qfalse); return val; } /* * call-seq: * obj.class_variable_defined?(symbol) => true or false * * Returns true if the given class variable is defined * in obj. * * class Fred * @@foo = 99 * end * Fred.class_variable_defined?(:@@foo) #=> true * Fred.class_variable_defined?(:@@bar) #=> false */ static VALUE rb_mod_cvar_defined(obj, iv) VALUE obj, iv; { ID id = rb_to_id(iv); if (!rb_is_class_id(id)) { rb_name_error(id, "`%s' is not allowed as a class variable name", rb_id2name(id)); } return rb_cvar_defined(obj, id); } static VALUE convert_type(val, tname, method, raise) VALUE val; const char *tname, *method; int raise; { ID m; m = rb_intern(method); if (!rb_respond_to(val, m)) { if (raise) { rb_raise(rb_eTypeError, "can't convert %s into %s", NIL_P(val) ? "nil" : val == Qtrue ? "true" : val == Qfalse ? "false" : rb_obj_classname(val), tname); } else { return Qnil; } } return rb_funcall(val, m, 0); } VALUE rb_convert_type(val, type, tname, method) VALUE val; int type; const char *tname, *method; { VALUE v; if (TYPE(val) == type) return val; v = convert_type(val, tname, method, Qtrue); if (TYPE(v) != type) { rb_raise(rb_eTypeError, "%s#%s should return %s", rb_obj_classname(val), method, tname); } return v; } VALUE rb_check_convert_type(val, type, tname, method) VALUE val; int type; const char *tname, *method; { VALUE v; /* always convert T_DATA */ if (TYPE(val) == type && type != T_DATA) return val; v = convert_type(val, tname, method, Qfalse); if (NIL_P(v)) return Qnil; if (TYPE(v) != type) { rb_raise(rb_eTypeError, "%s#%s should return %s", rb_obj_classname(val), method, tname); } return v; } static VALUE rb_to_integer(val, method) VALUE val; const char *method; { VALUE v = convert_type(val, "Integer", method, Qtrue); if (!rb_obj_is_kind_of(v, rb_cInteger)) { rb_raise(rb_eTypeError, "%s#%s should return Integer", rb_obj_classname(val), method); } return v; } VALUE rb_to_int(val) VALUE val; { return rb_to_integer(val, "to_int"); } VALUE rb_Integer(val) VALUE val; { VALUE tmp; switch (TYPE(val)) { case T_FLOAT: if (RFLOAT(val)->value <= (double)FIXNUM_MAX && RFLOAT(val)->value >= (double)FIXNUM_MIN) { break; } return rb_dbl2big(RFLOAT(val)->value); case T_FIXNUM: case T_BIGNUM: return val; case T_STRING: return rb_str_to_inum(val, 0, Qtrue); default: break; } tmp = convert_type(val, "Integer", "to_int", Qfalse); if (NIL_P(tmp)) { return rb_to_integer(val, "to_i"); } return tmp; } /* * call-seq: * Integer(arg) => integer * * Converts arg to a Fixnum or Bignum. * Numeric types are converted directly (with floating point numbers * being truncated). If arg is a String, leading * radix indicators (0, 0b, and * 0x) are honored. Others are converted using * to_int and to_i. This behavior is * different from that of String#to_i. * * Integer(123.999) #=> 123 * Integer("0x1a") #=> 26 * Integer(Time.new) #=> 1049896590 */ static VALUE rb_f_integer(obj, arg) VALUE obj, arg; { return rb_Integer(arg); } double rb_cstr_to_dbl(p, badcheck) const char *p; int badcheck; { const char *q; char *end; double d; const char *ellipsis = ""; int w; #define OutOfRange() (((w = end - p) > 20) ? (w = 20, ellipsis = "...") : (ellipsis = "")) if (!p) return 0.0; q = p; if (badcheck) { while (ISSPACE(*p)) p++; } else { while (ISSPACE(*p) || *p == '_') p++; } errno = 0; d = strtod(p, &end); if (errno == ERANGE) { OutOfRange(); rb_warn("Float %.*s%s out of range", w, p, ellipsis); errno = 0; } if (p == end) { if (badcheck) { bad: rb_invalid_str(q, "Float()"); } return d; } if (*end) { char *buf = ALLOCA_N(char, strlen(p)+1); char *n = buf; while (p < end) *n++ = *p++; while (*p) { if (*p == '_') { /* remove underscores between digits */ if (badcheck) { if (n == buf || !ISDIGIT(n[-1])) goto bad; ++p; if (!ISDIGIT(*p)) goto bad; } else { while (*++p == '_'); continue; } } *n++ = *p++; } *n = '\0'; p = buf; d = strtod(p, &end); if (errno == ERANGE) { OutOfRange(); rb_warn("Float %.*s%s out of range", w, p, ellipsis); errno = 0; } if (badcheck) { if (!end || p == end) goto bad; while (*end && ISSPACE(*end)) end++; if (*end) goto bad; } } if (errno == ERANGE) { errno = 0; OutOfRange(); rb_raise(rb_eArgError, "Float %.*s%s out of range", w, q, ellipsis); } return d; } double rb_str_to_dbl(str, badcheck) VALUE str; int badcheck; { char *s; long len; StringValue(str); s = RSTRING(str)->ptr; len = RSTRING(str)->len; if (s) { if (s[len]) { /* no sentinel somehow */ char *p = ALLOCA_N(char, len+1); MEMCPY(p, s, char, len); p[len] = '\0'; s = p; } if (badcheck && len != strlen(s)) { rb_raise(rb_eArgError, "string for Float contains null byte"); } } return rb_cstr_to_dbl(s, badcheck); } VALUE rb_Float(val) VALUE val; { switch (TYPE(val)) { case T_FIXNUM: return rb_float_new((double)FIX2LONG(val)); case T_FLOAT: return val; case T_BIGNUM: return rb_float_new(rb_big2dbl(val)); case T_STRING: return rb_float_new(rb_str_to_dbl(val, Qtrue)); case T_NIL: rb_raise(rb_eTypeError, "can't convert nil into Float"); break; default: { VALUE f = rb_convert_type(val, T_FLOAT, "Float", "to_f"); if (isnan(RFLOAT(f)->value)) { rb_raise(rb_eArgError, "invalid value for Float()"); } return f; } } } /* * call-seq: * Float(arg) => float * * Returns arg converted to a float. Numeric types are converted * directly, the rest are converted using arg.to_f. As of Ruby * 1.8, converting nil generates a TypeError. * * Float(1) #=> 1.0 * Float("123.456") #=> 123.456 */ static VALUE rb_f_float(obj, arg) VALUE obj, arg; { return rb_Float(arg); } double rb_num2dbl(val) VALUE val; { switch (TYPE(val)) { case T_FLOAT: return RFLOAT(val)->value; case T_STRING: rb_raise(rb_eTypeError, "no implicit conversion to float from string"); break; case T_NIL: rb_raise(rb_eTypeError, "no implicit conversion to float from nil"); break; default: break; } return RFLOAT(rb_Float(val))->value; } char* rb_str2cstr(str, len) VALUE str; long *len; { StringValue(str); if (len) *len = RSTRING(str)->len; else if (RTEST(ruby_verbose) && RSTRING(str)->len != strlen(RSTRING(str)->ptr)) { rb_warn("string contains \\0 character"); } return RSTRING(str)->ptr; } VALUE rb_String(val) VALUE val; { return rb_convert_type(val, T_STRING, "String", "to_s"); } /* * call-seq: * String(arg) => string * * Converts arg to a String by calling its * to_s method. * * String(self) #=> "main" * String(self.class #=> "Object" * String(123456) #=> "123456" */ static VALUE rb_f_string(obj, arg) VALUE obj, arg; { return rb_String(arg); } #if 0 VALUE rb_Array(val) VALUE val; { VALUE tmp = rb_check_array_type(val); if (NIL_P(tmp)) { tmp = rb_check_convert_type(val, T_ARRAY, "Array", "to_a"); if (NIL_P(tmp)) { return rb_ary_new3(1, val); } } return tmp; } #endif /* * call-seq: * Array(arg) => array * * Returns arg as an Array. First tries to call * arg.to_ary, then arg.to_a. * If both fail, creates a single element array containing arg * (unless arg is nil). * * Array(1..5) #=> [1, 2, 3, 4, 5] */ static VALUE rb_f_array(obj, arg) VALUE obj, arg; { return rb_Array(arg); } static VALUE boot_defclass(name, super) char *name; VALUE super; { extern st_table *rb_class_tbl; VALUE obj = rb_class_boot(super); ID id = rb_intern(name); rb_name_class(obj, id); st_add_direct(rb_class_tbl, id, obj); rb_const_set((rb_cObject ? rb_cObject : obj), id, obj); return obj; } VALUE ruby_top_self; /* * Document-class: Class * * Classes in Ruby are first-class objects---each is an instance of * class Class. * * When a new class is created (typically using class Name ... * end), an object of type Class is created and * assigned to a global constant (Name in this case). When * Name.new is called to create a new object, the * new method in Class is run by default. * This can be demonstrated by overriding new in * Class: * * class Class * alias oldNew new * def new(*args) * print "Creating a new ", self.name, "\n" * oldNew(*args) * end * end * * * class Name * end * * * n = Name.new * * produces: * * Creating a new Name * * Classes, modules, and objects are interrelated. In the diagram * that follows, the vertical arrows represent inheritance, and the * parentheses meta-classes. All metaclasses are instances * of the class `Class'. * * +------------------+ * | | * Object---->(Object) | * ^ ^ ^ ^ | * | | | | | * | | +-----+ +---------+ | * | | | | | * | +-----------+ | | * | | | | | * +------+ | Module--->(Module) | * | | ^ ^ | * OtherClass-->(OtherClass) | | | * | | | * Class---->(Class) | * ^ | * | | * +----------------+ */ /* * Object is the parent class of all classes in Ruby. Its * methods are therefore available to all objects unless explicitly * overridden. * * Object mixes in the Kernel module, making * the built-in kernel functions globally accessible. Although the * instance methods of Object are defined by the * Kernel module, we have chosen to document them here for * clarity. * * In the descriptions of Object's methods, the parameter symbol refers * to a symbol, which is either a quoted string or a * Symbol (such as :name). */ void Init_Object() { VALUE metaclass; rb_cObject = boot_defclass("Object", 0); rb_cModule = boot_defclass("Module", rb_cObject); rb_cClass = boot_defclass("Class", rb_cModule); metaclass = rb_make_metaclass(rb_cObject, rb_cClass); metaclass = rb_make_metaclass(rb_cModule, metaclass); metaclass = rb_make_metaclass(rb_cClass, metaclass); rb_mKernel = rb_define_module("Kernel"); rb_include_module(rb_cObject, rb_mKernel); rb_define_alloc_func(rb_cObject, rb_class_allocate_instance); rb_define_private_method(rb_cObject, "initialize", rb_obj_dummy, 0); rb_define_private_method(rb_cClass, "inherited", rb_obj_dummy, 1); rb_define_private_method(rb_cModule, "included", rb_obj_dummy, 1); rb_define_private_method(rb_cModule, "extended", rb_obj_dummy, 1); rb_define_private_method(rb_cModule, "method_added", rb_obj_dummy, 1); rb_define_private_method(rb_cModule, "method_removed", rb_obj_dummy, 1); rb_define_private_method(rb_cModule, "method_undefined", rb_obj_dummy, 1); rb_define_method(rb_mKernel, "nil?", rb_false, 0); rb_define_method(rb_mKernel, "==", rb_obj_equal, 1); rb_define_method(rb_mKernel, "equal?", rb_obj_equal, 1); rb_define_method(rb_mKernel, "===", rb_equal, 1); rb_define_method(rb_mKernel, "=~", rb_obj_pattern_match, 1); rb_define_method(rb_mKernel, "eql?", rb_obj_equal, 1); rb_define_method(rb_mKernel, "id", rb_obj_id_obsolete, 0); rb_define_method(rb_mKernel, "type", rb_obj_type, 0); rb_define_method(rb_mKernel, "class", rb_obj_class, 0); rb_define_method(rb_mKernel, "clone", rb_obj_clone, 0); rb_define_method(rb_mKernel, "dup", rb_obj_dup, 0); rb_define_method(rb_mKernel, "initialize_copy", rb_obj_init_copy, 1); rb_define_method(rb_mKernel, "taint", rb_obj_taint, 0); rb_define_method(rb_mKernel, "tainted?", rb_obj_tainted, 0); rb_define_method(rb_mKernel, "untaint", rb_obj_untaint, 0); rb_define_method(rb_mKernel, "freeze", rb_obj_freeze, 0); rb_define_method(rb_mKernel, "frozen?", rb_obj_frozen_p, 0); rb_define_method(rb_mKernel, "to_a", rb_any_to_a, 0); /* to be removed */ rb_define_method(rb_mKernel, "to_s", rb_any_to_s, 0); rb_define_method(rb_mKernel, "inspect", rb_obj_inspect, 0); rb_define_method(rb_mKernel, "methods", rb_obj_methods, -1); rb_define_method(rb_mKernel, "singleton_methods", rb_obj_singleton_methods, -1); /* in class.c */ rb_define_method(rb_mKernel, "protected_methods", rb_obj_protected_methods, -1); rb_define_method(rb_mKernel, "private_methods", rb_obj_private_methods, -1); rb_define_method(rb_mKernel, "public_methods", rb_obj_public_methods, -1); rb_define_method(rb_mKernel, "instance_variables", rb_obj_instance_variables, 0); /* in variable.c */ rb_define_method(rb_mKernel, "instance_variable_get", rb_obj_ivar_get, 1); rb_define_method(rb_mKernel, "instance_variable_set", rb_obj_ivar_set, 2); rb_define_method(rb_mKernel, "instance_variable_defined?", rb_obj_ivar_defined, 1); rb_define_private_method(rb_mKernel, "remove_instance_variable", rb_obj_remove_instance_variable, 1); /* in variable.c */ rb_define_method(rb_mKernel, "instance_of?", rb_obj_is_instance_of, 1); rb_define_method(rb_mKernel, "kind_of?", rb_obj_is_kind_of, 1); rb_define_method(rb_mKernel, "is_a?", rb_obj_is_kind_of, 1); rb_define_private_method(rb_mKernel, "singleton_method_added", rb_obj_dummy, 1); rb_define_private_method(rb_mKernel, "singleton_method_removed", rb_obj_dummy, 1); rb_define_private_method(rb_mKernel, "singleton_method_undefined", rb_obj_dummy, 1); rb_define_global_function("sprintf", rb_f_sprintf, -1); /* in sprintf.c */ rb_define_global_function("format", rb_f_sprintf, -1); /* in sprintf.c */ rb_define_global_function("Integer", rb_f_integer, 1); rb_define_global_function("Float", rb_f_float, 1); rb_define_global_function("String", rb_f_string, 1); rb_define_global_function("Array", rb_f_array, 1); rb_cNilClass = rb_define_class("NilClass", rb_cObject); rb_define_method(rb_cNilClass, "to_i", nil_to_i, 0); rb_define_method(rb_cNilClass, "to_f", nil_to_f, 0); rb_define_method(rb_cNilClass, "to_s", nil_to_s, 0); rb_define_method(rb_cNilClass, "to_a", nil_to_a, 0); rb_define_method(rb_cNilClass, "inspect", nil_inspect, 0); rb_define_method(rb_cNilClass, "&", false_and, 1); rb_define_method(rb_cNilClass, "|", false_or, 1); rb_define_method(rb_cNilClass, "^", false_xor, 1); rb_define_method(rb_cNilClass, "nil?", rb_true, 0); rb_undef_alloc_func(rb_cNilClass); rb_undef_method(CLASS_OF(rb_cNilClass), "new"); rb_define_global_const("NIL", Qnil); rb_cSymbol = rb_define_class("Symbol", rb_cObject); rb_define_singleton_method(rb_cSymbol, "all_symbols", rb_sym_all_symbols, 0); /* in parse.y */ rb_undef_alloc_func(rb_cSymbol); rb_undef_method(CLASS_OF(rb_cSymbol), "new"); rb_define_method(rb_cSymbol, "to_i", sym_to_i, 0); rb_define_method(rb_cSymbol, "to_int", sym_to_int, 0); rb_define_method(rb_cSymbol, "inspect", sym_inspect, 0); rb_define_method(rb_cSymbol, "to_s", sym_to_s, 0); rb_define_method(rb_cSymbol, "id2name", sym_to_s, 0); rb_define_method(rb_cSymbol, "to_sym", sym_to_sym, 0); rb_define_method(rb_cSymbol, "===", rb_obj_equal, 1); rb_define_method(rb_cModule, "freeze", rb_mod_freeze, 0); rb_define_method(rb_cModule, "===", rb_mod_eqq, 1); rb_define_method(rb_cModule, "==", rb_obj_equal, 1); rb_define_method(rb_cModule, "<=>", rb_mod_cmp, 1); rb_define_method(rb_cModule, "<", rb_mod_lt, 1); rb_define_method(rb_cModule, "<=", rb_class_inherited_p, 1); rb_define_method(rb_cModule, ">", rb_mod_gt, 1); rb_define_method(rb_cModule, ">=", rb_mod_ge, 1); rb_define_method(rb_cModule, "initialize_copy", rb_mod_init_copy, 1); /* in class.c */ rb_define_method(rb_cModule, "to_s", rb_mod_to_s, 0); rb_define_method(rb_cModule, "included_modules", rb_mod_included_modules, 0); /* in class.c */ rb_define_method(rb_cModule, "include?", rb_mod_include_p, 1); /* in class.c */ rb_define_method(rb_cModule, "name", rb_mod_name, 0); /* in variable.c */ rb_define_method(rb_cModule, "ancestors", rb_mod_ancestors, 0); /* in class.c */ rb_define_private_method(rb_cModule, "attr", rb_mod_attr, -1); rb_define_private_method(rb_cModule, "attr_reader", rb_mod_attr_reader, -1); rb_define_private_method(rb_cModule, "attr_writer", rb_mod_attr_writer, -1); rb_define_private_method(rb_cModule, "attr_accessor", rb_mod_attr_accessor, -1); rb_define_alloc_func(rb_cModule, rb_module_s_alloc); rb_define_method(rb_cModule, "initialize", rb_mod_initialize, 0); rb_define_method(rb_cModule, "instance_methods", rb_class_instance_methods, -1); /* in class.c */ rb_define_method(rb_cModule, "public_instance_methods", rb_class_public_instance_methods, -1); /* in class.c */ rb_define_method(rb_cModule, "protected_instance_methods", rb_class_protected_instance_methods, -1); /* in class.c */ rb_define_method(rb_cModule, "private_instance_methods", rb_class_private_instance_methods, -1); /* in class.c */ rb_define_method(rb_cModule, "class_variable_defined?", rb_mod_cvar_defined, 1); rb_define_method(rb_cModule, "constants", rb_mod_constants, 0); /* in variable.c */ rb_define_method(rb_cModule, "const_get", rb_mod_const_get, 1); rb_define_method(rb_cModule, "const_set", rb_mod_const_set, 2); rb_define_method(rb_cModule, "const_defined?", rb_mod_const_defined, 1); rb_define_private_method(rb_cModule, "remove_const", rb_mod_remove_const, 1); /* in variable.c */ rb_define_method(rb_cModule, "const_missing", rb_mod_const_missing, 1); /* in variable.c */ rb_define_method(rb_cModule, "class_variables", rb_mod_class_variables, 0); /* in variable.c */ rb_define_private_method(rb_cModule, "remove_class_variable", rb_mod_remove_cvar, 1); /* in variable.c */ rb_define_private_method(rb_cModule, "class_variable_get", rb_mod_cvar_get, 1); rb_define_private_method(rb_cModule, "class_variable_set", rb_mod_cvar_set, 2); rb_define_method(rb_cClass, "allocate", rb_obj_alloc, 0); rb_define_method(rb_cClass, "new", rb_class_new_instance, -1); rb_define_method(rb_cClass, "initialize", rb_class_initialize, -1); rb_define_method(rb_cClass, "initialize_copy", rb_class_init_copy, 1); /* in class.c */ rb_define_method(rb_cClass, "superclass", rb_class_superclass, 0); rb_define_alloc_func(rb_cClass, rb_class_s_alloc); rb_undef_method(rb_cClass, "extend_object"); rb_undef_method(rb_cClass, "append_features"); rb_cData = rb_define_class("Data", rb_cObject); rb_undef_alloc_func(rb_cData); rb_global_variable(&ruby_top_self); ruby_top_self = rb_obj_alloc(rb_cObject); rb_define_singleton_method(ruby_top_self, "to_s", main_to_s, 0); rb_cTrueClass = rb_define_class("TrueClass", rb_cObject); rb_define_method(rb_cTrueClass, "to_s", true_to_s, 0); rb_define_method(rb_cTrueClass, "&", true_and, 1); rb_define_method(rb_cTrueClass, "|", true_or, 1); rb_define_method(rb_cTrueClass, "^", true_xor, 1); rb_undef_alloc_func(rb_cTrueClass); rb_undef_method(CLASS_OF(rb_cTrueClass), "new"); rb_define_global_const("TRUE", Qtrue); rb_cFalseClass = rb_define_class("FalseClass", rb_cObject); rb_define_method(rb_cFalseClass, "to_s", false_to_s, 0); rb_define_method(rb_cFalseClass, "&", false_and, 1); rb_define_method(rb_cFalseClass, "|", false_or, 1); rb_define_method(rb_cFalseClass, "^", false_xor, 1); rb_undef_alloc_func(rb_cFalseClass); rb_undef_method(CLASS_OF(rb_cFalseClass), "new"); rb_define_global_const("FALSE", Qfalse); id_eq = rb_intern("=="); id_eql = rb_intern("eql?"); id_inspect = rb_intern("inspect"); id_init_copy = rb_intern("initialize_copy"); } /********************************************************************** pack.c - $Author: shyouhei $ $Date: 2009-02-19 10:01:35 +0100 (Thu, 19 Feb 2009) $ created at: Thu Feb 10 15:17:05 JST 1994 Copyright (C) 1993-2003 Yukihiro Matsumoto **********************************************************************/ #include "ruby.h" #include #include #define SIZE16 2 #define SIZE32 4 #if SIZEOF_SHORT != 2 || SIZEOF_LONG != 4 # define NATINT_PACK #endif #ifdef NATINT_PACK # define OFF16B(p) ((char*)(p) + (natint?0:(sizeof(short) - SIZE16))) # define OFF32B(p) ((char*)(p) + (natint?0:(sizeof(long) - SIZE32))) # define NATINT_LEN(type,len) (natint?sizeof(type):(len)) # ifdef WORDS_BIGENDIAN # define OFF16(p) OFF16B(p) # define OFF32(p) OFF32B(p) # endif # define NATINT_HTOVS(x) (natint?htovs(x):htov16(x)) # define NATINT_HTOVL(x) (natint?htovl(x):htov32(x)) # define NATINT_HTONS(x) (natint?htons(x):hton16(x)) # define NATINT_HTONL(x) (natint?htonl(x):hton32(x)) #else # define NATINT_LEN(type,len) sizeof(type) # define NATINT_HTOVS(x) htovs(x) # define NATINT_HTOVL(x) htovl(x) # define NATINT_HTONS(x) htons(x) # define NATINT_HTONL(x) htonl(x) #endif #ifndef OFF16 # define OFF16(p) (char*)(p) # define OFF32(p) (char*)(p) #endif #ifndef OFF16B # define OFF16B(p) (char*)(p) # define OFF32B(p) (char*)(p) #endif #define define_swapx(x, xtype) \ static xtype \ TOKEN_PASTE(swap,x)(z) \ xtype z; \ { \ xtype r; \ xtype *zp; \ unsigned char *s, *t; \ int i; \ \ zp = xmalloc(sizeof(xtype)); \ *zp = z; \ s = (unsigned char*)zp; \ t = xmalloc(sizeof(xtype)); \ for (i=0; i>8)&0xFF)) #endif #if SIZEOF_SHORT == 2 #define swaps(x) swap16(x) #else #if SIZEOF_SHORT == 4 #define swaps(x) ((((x)&0xFF)<<24) \ |(((x)>>24)&0xFF) \ |(((x)&0x0000FF00)<<8) \ |(((x)&0x00FF0000)>>8) ) #else define_swapx(s,short) #endif #endif #ifndef swap32 #define swap32(x) ((((x)&0xFF)<<24) \ |(((x)>>24)&0xFF) \ |(((x)&0x0000FF00)<<8) \ |(((x)&0x00FF0000)>>8) ) #endif #if SIZEOF_LONG == 4 #define swapl(x) swap32(x) #else #if SIZEOF_LONG == 8 #define swapl(x) ((((x)&0x00000000000000FF)<<56) \ |(((x)&0xFF00000000000000)>>56) \ |(((x)&0x000000000000FF00)<<40) \ |(((x)&0x00FF000000000000)>>40) \ |(((x)&0x0000000000FF0000)<<24) \ |(((x)&0x0000FF0000000000)>>24) \ |(((x)&0x00000000FF000000)<<8) \ |(((x)&0x000000FF00000000)>>8)) #else define_swapx(l,long) #endif #endif #if SIZEOF_FLOAT == 4 #if SIZEOF_LONG == 4 /* SIZEOF_FLOAT == 4 == SIZEOF_LONG */ #define swapf(x) swapl(x) #define FLOAT_SWAPPER unsigned long #else #if SIZEOF_SHORT == 4 /* SIZEOF_FLOAT == 4 == SIZEOF_SHORT */ #define swapf(x) swaps(x) #define FLOAT_SWAPPER unsigned short #else /* SIZEOF_FLOAT == 4 but undivide by known size of int */ define_swapx(f,float) #endif /* #if SIZEOF_SHORT == 4 */ #endif /* #if SIZEOF_LONG == 4 */ #else /* SIZEOF_FLOAT != 4 */ define_swapx(f,float) #endif /* #if SIZEOF_FLOAT == 4 */ #if SIZEOF_DOUBLE == 8 #if SIZEOF_LONG == 8 /* SIZEOF_DOUBLE == 8 == SIZEOF_LONG */ #define swapd(x) swapl(x) #define DOUBLE_SWAPPER unsigned long #else #if SIZEOF_LONG == 4 /* SIZEOF_DOUBLE == 8 && 4 == SIZEOF_LONG */ static double swapd(d) const double d; { double dtmp = d; unsigned long utmp[2]; unsigned long utmp0; utmp[0] = 0; utmp[1] = 0; memcpy(utmp,&dtmp,sizeof(double)); utmp0 = utmp[0]; utmp[0] = swapl(utmp[1]); utmp[1] = swapl(utmp0); memcpy(&dtmp,utmp,sizeof(double)); return dtmp; } #else #if SIZEOF_SHORT == 4 /* SIZEOF_DOUBLE == 8 && 4 == SIZEOF_SHORT */ static double swapd(d) const double d; { double dtmp = d; unsigned short utmp[2]; unsigned short utmp0; utmp[0] = 0; utmp[1] = 0; memcpy(utmp,&dtmp,sizeof(double)); utmp0 = utmp[0]; utmp[0] = swaps(utmp[1]); utmp[1] = swaps(utmp0); memcpy(&dtmp,utmp,sizeof(double)); return dtmp; } #else /* SIZEOF_DOUBLE == 8 but undivied by known size of int */ define_swapx(d, double) #endif /* #if SIZEOF_SHORT == 4 */ #endif /* #if SIZEOF_LONG == 4 */ #endif /* #if SIZEOF_LONG == 8 */ #else /* SIZEOF_DOUBLE != 8 */ define_swapx(d, double) #endif /* #if SIZEOF_DOUBLE == 8 */ #undef define_swapx #ifdef DYNAMIC_ENDIAN #ifdef ntohs #undef ntohs #undef ntohl #undef htons #undef htonl #endif static int endian() { static int init = 0; static int endian_value; char *p; if (init) return endian_value; init = 1; p = (char*)&init; return endian_value = p[0]?0:1; } #define ntohs(x) (endian()?(x):swaps(x)) #define ntohl(x) (endian()?(x):swapl(x)) #define ntohf(x) (endian()?(x):swapf(x)) #define ntohd(x) (endian()?(x):swapd(x)) #define htons(x) (endian()?(x):swaps(x)) #define htonl(x) (endian()?(x):swapl(x)) #define htonf(x) (endian()?(x):swapf(x)) #define htond(x) (endian()?(x):swapd(x)) #define htovs(x) (endian()?swaps(x):(x)) #define htovl(x) (endian()?swapl(x):(x)) #define htovf(x) (endian()?swapf(x):(x)) #define htovd(x) (endian()?swapd(x):(x)) #define vtohs(x) (endian()?swaps(x):(x)) #define vtohl(x) (endian()?swapl(x):(x)) #define vtohf(x) (endian()?swapf(x):(x)) #define vtohd(x) (endian()?swapd(x):(x)) # ifdef NATINT_PACK #define htov16(x) (endian()?swap16(x):(x)) #define htov32(x) (endian()?swap32(x):(x)) #define hton16(x) (endian()?(x):swap16(x)) #define hton32(x) (endian()?(x):swap32(x)) # endif #else #ifdef WORDS_BIGENDIAN #ifndef ntohs #define ntohs(x) (x) #define ntohl(x) (x) #define htons(x) (x) #define htonl(x) (x) #endif #define ntohf(x) (x) #define ntohd(x) (x) #define htonf(x) (x) #define htond(x) (x) #define htovs(x) swaps(x) #define htovl(x) swapl(x) #define htovf(x) swapf(x) #define htovd(x) swapd(x) #define vtohs(x) swaps(x) #define vtohl(x) swapl(x) #define vtohf(x) swapf(x) #define vtohd(x) swapd(x) # ifdef NATINT_PACK #define htov16(x) swap16(x) #define htov32(x) swap32(x) #define hton16(x) (x) #define hton32(x) (x) # endif #else /* LITTLE ENDIAN */ #ifdef ntohs #undef ntohs #undef ntohl #undef htons #undef htonl #endif #define ntohs(x) swaps(x) #define ntohl(x) swapl(x) #define htons(x) swaps(x) #define htonl(x) swapl(x) #define ntohf(x) swapf(x) #define ntohd(x) swapd(x) #define htonf(x) swapf(x) #define htond(x) swapd(x) #define htovs(x) (x) #define htovl(x) (x) #define htovf(x) (x) #define htovd(x) (x) #define vtohs(x) (x) #define vtohl(x) (x) #define vtohf(x) (x) #define vtohd(x) (x) # ifdef NATINT_PACK #define htov16(x) (x) #define htov32(x) (x) #define hton16(x) swap16(x) #define hton32(x) swap32(x) # endif #endif #endif #ifdef FLOAT_SWAPPER #define FLOAT_CONVWITH(y) FLOAT_SWAPPER y; #define HTONF(x,y) (memcpy(&y,&x,sizeof(float)), \ y = htonf((FLOAT_SWAPPER)y), \ memcpy(&x,&y,sizeof(float)), \ x) #define HTOVF(x,y) (memcpy(&y,&x,sizeof(float)), \ y = htovf((FLOAT_SWAPPER)y), \ memcpy(&x,&y,sizeof(float)), \ x) #define NTOHF(x,y) (memcpy(&y,&x,sizeof(float)), \ y = ntohf((FLOAT_SWAPPER)y), \ memcpy(&x,&y,sizeof(float)), \ x) #define VTOHF(x,y) (memcpy(&y,&x,sizeof(float)), \ y = vtohf((FLOAT_SWAPPER)y), \ memcpy(&x,&y,sizeof(float)), \ x) #else #define FLOAT_CONVWITH(y) #define HTONF(x,y) htonf(x) #define HTOVF(x,y) htovf(x) #define NTOHF(x,y) ntohf(x) #define VTOHF(x,y) vtohf(x) #endif #ifdef DOUBLE_SWAPPER #define DOUBLE_CONVWITH(y) DOUBLE_SWAPPER y; #define HTOND(x,y) (memcpy(&y,&x,sizeof(double)), \ y = htond((DOUBLE_SWAPPER)y), \ memcpy(&x,&y,sizeof(double)), \ x) #define HTOVD(x,y) (memcpy(&y,&x,sizeof(double)), \ y = htovd((DOUBLE_SWAPPER)y), \ memcpy(&x,&y,sizeof(double)), \ x) #define NTOHD(x,y) (memcpy(&y,&x,sizeof(double)), \ y = ntohd((DOUBLE_SWAPPER)y), \ memcpy(&x,&y,sizeof(double)), \ x) #define VTOHD(x,y) (memcpy(&y,&x,sizeof(double)), \ y = vtohd((DOUBLE_SWAPPER)y), \ memcpy(&x,&y,sizeof(double)), \ x) #else #define DOUBLE_CONVWITH(y) #define HTOND(x,y) htond(x) #define HTOVD(x,y) htovd(x) #define NTOHD(x,y) ntohd(x) #define VTOHD(x,y) vtohd(x) #endif unsigned long rb_big2ulong_pack _((VALUE x)); static unsigned long num2i32(x) VALUE x; { x = rb_to_int(x); /* is nil OK? (should not) */ if (FIXNUM_P(x)) return FIX2LONG(x); if (TYPE(x) == T_BIGNUM) { return rb_big2ulong_pack(x); } rb_raise(rb_eTypeError, "can't convert %s to `integer'", rb_obj_classname(x)); return 0; /* not reached */ } #if SIZEOF_LONG == SIZE32 # define EXTEND32(x) #else /* invariant in modulo 1<<31 */ # define EXTEND32(x) do { if (!natint) {(x) = (((1L<<31)-1-(x))^~(~0L<<31));}} while(0) #endif #if SIZEOF_SHORT == SIZE16 # define EXTEND16(x) #else # define EXTEND16(x) do { if (!natint) {(x) = (short)(((1<<15)-1-(x))^~(~0<<15));}} while(0) #endif #ifdef HAVE_LONG_LONG # define QUAD_SIZE sizeof(LONG_LONG) #else # define QUAD_SIZE 8 #endif static const char toofew[] = "too few arguments"; static void encodes _((VALUE,char*,long,int)); static void qpencode _((VALUE,VALUE,long)); static int uv_to_utf8 _((char*,unsigned long)); static unsigned long utf8_to_uv _((char*,long*)); /* * call-seq: * arr.pack ( aTemplateString ) -> aBinaryString * * Packs the contents of arr into a binary sequence according to * the directives in aTemplateString (see the table below) * Directives ``A,'' ``a,'' and ``Z'' may be followed by a count, * which gives the width of the resulting field. The remaining * directives also may take a count, indicating the number of array * elements to convert. If the count is an asterisk * (``*''), all remaining array elements will be * converted. Any of the directives ``sSiIlL'' may be * followed by an underscore (``_'') to use the underlying * platform's native size for the specified type; otherwise, they use a * platform-independent size. Spaces are ignored in the template * string. See also String#unpack. * * a = [ "a", "b", "c" ] * n = [ 65, 66, 67 ] * a.pack("A3A3A3") #=> "a b c " * a.pack("a3a3a3") #=> "a\000\000b\000\000c\000\000" * n.pack("ccc") #=> "ABC" * * Directives for +pack+. * * Directive Meaning * --------------------------------------------------------------- * @ | Moves to absolute position * A | ASCII string (space padded, count is width) * a | ASCII string (null padded, count is width) * B | Bit string (descending bit order) * b | Bit string (ascending bit order) * C | Unsigned char * c | Char * D, d | Double-precision float, native format * E | Double-precision float, little-endian byte order * e | Single-precision float, little-endian byte order * F, f | Single-precision float, native format * G | Double-precision float, network (big-endian) byte order * g | Single-precision float, network (big-endian) byte order * H | Hex string (high nibble first) * h | Hex string (low nibble first) * I | Unsigned integer * i | Integer * L | Unsigned long * l | Long * M | Quoted printable, MIME encoding (see RFC2045) * m | Base64 encoded string * N | Long, network (big-endian) byte order * n | Short, network (big-endian) byte-order * P | Pointer to a structure (fixed-length string) * p | Pointer to a null-terminated string * Q, q | 64-bit number * S | Unsigned short * s | Short * U | UTF-8 * u | UU-encoded string * V | Long, little-endian byte order * v | Short, little-endian byte order * w | BER-compressed integer\fnm * X | Back up a byte * x | Null byte * Z | Same as ``a'', except that null is added with * */ static VALUE pack_pack(ary, fmt) VALUE ary, fmt; { static char *nul10 = "\0\0\0\0\0\0\0\0\0\0"; static char *spc10 = " "; char *p, *pend; VALUE res, from, associates = 0; char type; long items, len, idx, plen; char *ptr; #ifdef NATINT_PACK int natint; /* native integer */ #endif StringValue(fmt); p = RSTRING(fmt)->ptr; pend = p + RSTRING(fmt)->len; res = rb_str_buf_new(0); items = RARRAY(ary)->len; idx = 0; #define TOO_FEW (rb_raise(rb_eArgError, toofew), 0) #define THISFROM (items > 0 ? RARRAY(ary)->ptr[idx] : TOO_FEW) #define NEXTFROM (items-- > 0 ? RARRAY(ary)->ptr[idx++] : TOO_FEW) while (p < pend) { if (RSTRING(fmt)->ptr + RSTRING(fmt)->len != pend) { rb_raise(rb_eRuntimeError, "format string modified"); } type = *p++; /* get data type */ #ifdef NATINT_PACK natint = 0; #endif if (ISSPACE(type)) continue; if (type == '#') { while ((p < pend) && (*p != '\n')) { p++; } continue; } if (*p == '_' || *p == '!') { const char *natstr = "sSiIlL"; if (strchr(natstr, type)) { #ifdef NATINT_PACK natint = 1; #endif p++; } else { rb_raise(rb_eArgError, "'%c' allowed only after types %s", *p, natstr); } } if (*p == '*') { /* set data length */ len = strchr("@Xxu", type) ? 0 : strchr("PMm", type) ? 1 : items; p++; } else if (ISDIGIT(*p)) { len = strtoul(p, (char**)&p, 10); } else { len = 1; } switch (type) { case 'A': case 'a': case 'Z': case 'B': case 'b': case 'H': case 'h': from = NEXTFROM; if (NIL_P(from)) { ptr = ""; plen = 0; } else { StringValue(from); ptr = RSTRING(from)->ptr; plen = RSTRING(from)->len; OBJ_INFECT(res, from); } if (p[-1] == '*') len = plen; switch (type) { case 'a': /* arbitrary binary string (null padded) */ case 'A': /* ASCII string (space padded) */ case 'Z': /* null terminated ASCII string */ if (plen >= len) { rb_str_buf_cat(res, ptr, len); if (p[-1] == '*' && type == 'Z') rb_str_buf_cat(res, nul10, 1); } else { rb_str_buf_cat(res, ptr, plen); len -= plen; while (len >= 10) { rb_str_buf_cat(res, (type == 'A')?spc10:nul10, 10); len -= 10; } rb_str_buf_cat(res, (type == 'A')?spc10:nul10, len); } break; case 'b': /* bit string (ascending) */ { int byte = 0; long i, j = 0; if (len > plen) { j = (len - plen + 1)/2; len = plen; } for (i=0; i++ < len; ptr++) { if (*ptr & 1) byte |= 128; if (i & 7) byte >>= 1; else { char c = byte & 0xff; rb_str_buf_cat(res, &c, 1); byte = 0; } } if (len & 7) { char c; byte >>= 7 - (len & 7); c = byte & 0xff; rb_str_buf_cat(res, &c, 1); } len = j; goto grow; } break; case 'B': /* bit string (descending) */ { int byte = 0; long i, j = 0; if (len > plen) { j = (len - plen + 1)/2; len = plen; } for (i=0; i++ < len; ptr++) { byte |= *ptr & 1; if (i & 7) byte <<= 1; else { char c = byte & 0xff; rb_str_buf_cat(res, &c, 1); byte = 0; } } if (len & 7) { char c; byte <<= 7 - (len & 7); c = byte & 0xff; rb_str_buf_cat(res, &c, 1); } len = j; goto grow; } break; case 'h': /* hex string (low nibble first) */ { int byte = 0; long i, j = 0; if (len > plen) { j = (len + 1) / 2 - (plen + 1) / 2; len = plen; } for (i=0; i++ < len; ptr++) { if (ISALPHA(*ptr)) byte |= (((*ptr & 15) + 9) & 15) << 4; else byte |= (*ptr & 15) << 4; if (i & 1) byte >>= 4; else { char c = byte & 0xff; rb_str_buf_cat(res, &c, 1); byte = 0; } } if (len & 1) { char c = byte & 0xff; rb_str_buf_cat(res, &c, 1); } len = j; goto grow; } break; case 'H': /* hex string (high nibble first) */ { int byte = 0; long i, j = 0; if (len > plen) { j = (len + 1) / 2 - (plen + 1) / 2; len = plen; } for (i=0; i++ < len; ptr++) { if (ISALPHA(*ptr)) byte |= ((*ptr & 15) + 9) & 15; else byte |= *ptr & 15; if (i & 1) byte <<= 4; else { char c = byte & 0xff; rb_str_buf_cat(res, &c, 1); byte = 0; } } if (len & 1) { char c = byte & 0xff; rb_str_buf_cat(res, &c, 1); } len = j; goto grow; } break; } break; case 'c': /* signed char */ case 'C': /* unsigned char */ while (len-- > 0) { char c; from = NEXTFROM; c = num2i32(from); rb_str_buf_cat(res, &c, sizeof(char)); } break; case 's': /* signed short */ case 'S': /* unsigned short */ while (len-- > 0) { short s; from = NEXTFROM; s = num2i32(from); rb_str_buf_cat(res, OFF16(&s), NATINT_LEN(short,2)); } break; case 'i': /* signed int */ case 'I': /* unsigned int */ while (len-- > 0) { long i; from = NEXTFROM; i = num2i32(from); rb_str_buf_cat(res, OFF32(&i), NATINT_LEN(int,4)); } break; case 'l': /* signed long */ case 'L': /* unsigned long */ while (len-- > 0) { long l; from = NEXTFROM; l = num2i32(from); rb_str_buf_cat(res, OFF32(&l), NATINT_LEN(long,4)); } break; case 'q': /* signed quad (64bit) int */ case 'Q': /* unsigned quad (64bit) int */ while (len-- > 0) { char tmp[QUAD_SIZE]; from = NEXTFROM; rb_quad_pack(tmp, from); rb_str_buf_cat(res, (char*)&tmp, QUAD_SIZE); } break; case 'n': /* unsigned short (network byte-order) */ while (len-- > 0) { unsigned short s; from = NEXTFROM; s = num2i32(from); s = NATINT_HTONS(s); rb_str_buf_cat(res, OFF16(&s), NATINT_LEN(short,2)); } break; case 'N': /* unsigned long (network byte-order) */ while (len-- > 0) { unsigned long l; from = NEXTFROM; l = num2i32(from); l = NATINT_HTONL(l); rb_str_buf_cat(res, OFF32(&l), NATINT_LEN(long,4)); } break; case 'v': /* unsigned short (VAX byte-order) */ while (len-- > 0) { unsigned short s; from = NEXTFROM; s = num2i32(from); s = NATINT_HTOVS(s); rb_str_buf_cat(res, OFF16(&s), NATINT_LEN(short,2)); } break; case 'V': /* unsigned long (VAX byte-order) */ while (len-- > 0) { unsigned long l; from = NEXTFROM; l = num2i32(from); l = NATINT_HTOVL(l); rb_str_buf_cat(res, OFF32(&l), NATINT_LEN(long,4)); } break; case 'f': /* single precision float in native format */ case 'F': /* ditto */ while (len-- > 0) { float f; from = NEXTFROM; f = RFLOAT(rb_Float(from))->value; rb_str_buf_cat(res, (char*)&f, sizeof(float)); } break; case 'e': /* single precision float in VAX byte-order */ while (len-- > 0) { float f; FLOAT_CONVWITH(ftmp); from = NEXTFROM; f = RFLOAT(rb_Float(from))->value; f = HTOVF(f,ftmp); rb_str_buf_cat(res, (char*)&f, sizeof(float)); } break; case 'E': /* double precision float in VAX byte-order */ while (len-- > 0) { double d; DOUBLE_CONVWITH(dtmp); from = NEXTFROM; d = RFLOAT(rb_Float(from))->value; d = HTOVD(d,dtmp); rb_str_buf_cat(res, (char*)&d, sizeof(double)); } break; case 'd': /* double precision float in native format */ case 'D': /* ditto */ while (len-- > 0) { double d; from = NEXTFROM; d = RFLOAT(rb_Float(from))->value; rb_str_buf_cat(res, (char*)&d, sizeof(double)); } break; case 'g': /* single precision float in network byte-order */ while (len-- > 0) { float f; FLOAT_CONVWITH(ftmp); from = NEXTFROM; f = RFLOAT(rb_Float(from))->value; f = HTONF(f,ftmp); rb_str_buf_cat(res, (char*)&f, sizeof(float)); } break; case 'G': /* double precision float in network byte-order */ while (len-- > 0) { double d; DOUBLE_CONVWITH(dtmp); from = NEXTFROM; d = RFLOAT(rb_Float(from))->value; d = HTOND(d,dtmp); rb_str_buf_cat(res, (char*)&d, sizeof(double)); } break; case 'x': /* null byte */ grow: while (len >= 10) { rb_str_buf_cat(res, nul10, 10); len -= 10; } rb_str_buf_cat(res, nul10, len); break; case 'X': /* back up byte */ shrink: plen = RSTRING(res)->len; if (plen < len) rb_raise(rb_eArgError, "X outside of string"); RSTRING(res)->len = plen - len; RSTRING(res)->ptr[plen - len] = '\0'; break; case '@': /* null fill to absolute position */ len -= RSTRING(res)->len; if (len > 0) goto grow; len = -len; if (len > 0) goto shrink; break; case '%': rb_raise(rb_eArgError, "%% is not supported"); break; case 'U': /* Unicode character */ while (len-- > 0) { long l; char buf[8]; int le; from = NEXTFROM; from = rb_to_int(from); l = NUM2INT(from); if (l < 0) { rb_raise(rb_eRangeError, "pack(U): value out of range"); } le = uv_to_utf8(buf, l); rb_str_buf_cat(res, (char*)buf, le); } break; case 'u': /* uuencoded string */ case 'm': /* base64 encoded string */ from = NEXTFROM; StringValue(from); ptr = RSTRING(from)->ptr; plen = RSTRING(from)->len; if (len <= 2) len = 45; else len = len / 3 * 3; while (plen > 0) { long todo; if (plen > len) todo = len; else todo = plen; encodes(res, ptr, todo, type); plen -= todo; ptr += todo; } break; case 'M': /* quoted-printable encoded string */ from = rb_obj_as_string(NEXTFROM); if (len <= 1) len = 72; qpencode(res, from, len); break; case 'P': /* pointer to packed byte string */ from = THISFROM; if (!NIL_P(from)) { StringValue(from); if (RSTRING(from)->len < len) { rb_raise(rb_eArgError, "too short buffer for P(%ld for %ld)", RSTRING(from)->len, len); } } len = 1; /* FALL THROUGH */ case 'p': /* pointer to string */ while (len-- > 0) { char *t; from = NEXTFROM; if (NIL_P(from)) { t = 0; } else { t = StringValuePtr(from); } if (!associates) { associates = rb_ary_new(); } rb_ary_push(associates, from); rb_obj_taint(from); rb_str_buf_cat(res, (char*)&t, sizeof(char*)); } break; case 'w': /* BER compressed integer */ while (len-- > 0) { unsigned long ul; VALUE buf = rb_str_new(0, 0); char c, *bufs, *bufe; from = NEXTFROM; if (TYPE(from) == T_BIGNUM) { VALUE big128 = rb_uint2big(128); while (TYPE(from) == T_BIGNUM) { from = rb_big_divmod(from, big128); c = NUM2INT(RARRAY(from)->ptr[1]) | 0x80; /* mod */ rb_str_buf_cat(buf, &c, sizeof(char)); from = RARRAY(from)->ptr[0]; /* div */ } } { long l = NUM2LONG(from); if (l < 0) { rb_raise(rb_eArgError, "can't compress negative numbers"); } ul = l; } while (ul) { c = ((ul & 0x7f) | 0x80); rb_str_buf_cat(buf, &c, sizeof(char)); ul >>= 7; } if (RSTRING(buf)->len) { bufs = RSTRING(buf)->ptr; bufe = bufs + RSTRING(buf)->len - 1; *bufs &= 0x7f; /* clear continue bit */ while (bufs < bufe) { /* reverse */ c = *bufs; *bufs++ = *bufe; *bufe-- = c; } rb_str_buf_cat(res, RSTRING(buf)->ptr, RSTRING(buf)->len); } else { c = 0; rb_str_buf_cat(res, &c, sizeof(char)); } } break; default: break; } } if (associates) { rb_str_associate(res, associates); } OBJ_INFECT(res, fmt); return res; } static char uu_table[] = "`!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"; static char b64_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static void encodes(str, s, len, type) VALUE str; char *s; long len; int type; { char *buff = ALLOCA_N(char, len * 4 / 3 + 6); long i = 0; char *trans = type == 'u' ? uu_table : b64_table; int padding; if (type == 'u') { buff[i++] = len + ' '; padding = '`'; } else { padding = '='; } while (len >= 3) { buff[i++] = trans[077 & (*s >> 2)]; buff[i++] = trans[077 & (((*s << 4) & 060) | ((s[1] >> 4) & 017))]; buff[i++] = trans[077 & (((s[1] << 2) & 074) | ((s[2] >> 6) & 03))]; buff[i++] = trans[077 & s[2]]; s += 3; len -= 3; } if (len == 2) { buff[i++] = trans[077 & (*s >> 2)]; buff[i++] = trans[077 & (((*s << 4) & 060) | ((s[1] >> 4) & 017))]; buff[i++] = trans[077 & (((s[1] << 2) & 074) | (('\0' >> 6) & 03))]; buff[i++] = padding; } else if (len == 1) { buff[i++] = trans[077 & (*s >> 2)]; buff[i++] = trans[077 & (((*s << 4) & 060) | (('\0' >> 4) & 017))]; buff[i++] = padding; buff[i++] = padding; } buff[i++] = '\n'; rb_str_buf_cat(str, buff, i); } static char hex_table[] = "0123456789ABCDEF"; static void qpencode(str, from, len) VALUE str, from; long len; { char buff[1024]; long i = 0, n = 0, prev = EOF; unsigned char *s = (unsigned char*)RSTRING(from)->ptr; unsigned char *send = s + RSTRING(from)->len; while (s < send) { if ((*s > 126) || (*s < 32 && *s != '\n' && *s != '\t') || (*s == '=')) { buff[i++] = '='; buff[i++] = hex_table[*s >> 4]; buff[i++] = hex_table[*s & 0x0f]; n += 3; prev = EOF; } else if (*s == '\n') { if (prev == ' ' || prev == '\t') { buff[i++] = '='; buff[i++] = *s; } buff[i++] = *s; n = 0; prev = *s; } else { buff[i++] = *s; n++; prev = *s; } if (n > len) { buff[i++] = '='; buff[i++] = '\n'; n = 0; prev = '\n'; } if (i > 1024 - 5) { rb_str_buf_cat(str, buff, i); i = 0; } s++; } if (n > 0) { buff[i++] = '='; buff[i++] = '\n'; } if (i > 0) { rb_str_buf_cat(str, buff, i); } } static inline int hex2num(c) char c; { switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return c - '0'; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': return c - 'a' + 10; case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': return c - 'A' + 10; default: return -1; } } #define PACK_LENGTH_ADJUST_SIZE(sz) do { \ tmp = 0; \ if (len > (send-s)/sz) { \ if (!star) { \ tmp = len-(send-s)/sz; \ } \ len = (send-s)/sz; \ } \ } while (0) #ifdef NATINT_PACK #define PACK_LENGTH_ADJUST(type,sz) do { \ int t__len = NATINT_LEN(type,(sz)); \ PACK_LENGTH_ADJUST_SIZE(t__len); \ } while (0) #else #define PACK_LENGTH_ADJUST(type,sz) \ PACK_LENGTH_ADJUST_SIZE(sizeof(type)) #endif #define PACK_ITEM_ADJUST() while (tmp--) rb_ary_push(ary, Qnil) static VALUE infected_str_new(ptr, len, str) const char *ptr; long len; VALUE str; { VALUE s = rb_str_new(ptr, len); OBJ_INFECT(s, str); return s; } /* * call-seq: * str.unpack(format) => anArray * * Decodes str (which may contain binary data) according to the * format string, returning an array of each value extracted. The * format string consists of a sequence of single-character directives, * summarized in the table at the end of this entry. * Each directive may be followed * by a number, indicating the number of times to repeat with this * directive. An asterisk (``*'') will use up all * remaining elements. The directives sSiIlL may each be * followed by an underscore (``_'') to use the underlying * platform's native size for the specified type; otherwise, it uses a * platform-independent consistent size. Spaces are ignored in the * format string. See also Array#pack. * * "abc \0\0abc \0\0".unpack('A6Z6') #=> ["abc", "abc "] * "abc \0\0".unpack('a3a3') #=> ["abc", " \000\000"] * "abc \0abc \0".unpack('Z*Z*') #=> ["abc ", "abc "] * "aa".unpack('b8B8') #=> ["10000110", "01100001"] * "aaa".unpack('h2H2c') #=> ["16", "61", 97] * "\xfe\xff\xfe\xff".unpack('sS') #=> [-2, 65534] * "now=20is".unpack('M*') #=> ["now is"] * "whole".unpack('xax2aX2aX1aX2a') #=> ["h", "e", "l", "l", "o"] * * This table summarizes the various formats and the Ruby classes * returned by each. * * Format | Returns | Function * -------+---------+----------------------------------------- * A | String | with trailing nulls and spaces removed * -------+---------+----------------------------------------- * a | String | string * -------+---------+----------------------------------------- * B | String | extract bits from each character (msb first) * -------+---------+----------------------------------------- * b | String | extract bits from each character (lsb first) * -------+---------+----------------------------------------- * C | Fixnum | extract a character as an unsigned integer * -------+---------+----------------------------------------- * c | Fixnum | extract a character as an integer * -------+---------+----------------------------------------- * d,D | Float | treat sizeof(double) characters as * | | a native double * -------+---------+----------------------------------------- * E | Float | treat sizeof(double) characters as * | | a double in little-endian byte order * -------+---------+----------------------------------------- * e | Float | treat sizeof(float) characters as * | | a float in little-endian byte order * -------+---------+----------------------------------------- * f,F | Float | treat sizeof(float) characters as * | | a native float * -------+---------+----------------------------------------- * G | Float | treat sizeof(double) characters as * | | a double in network byte order * -------+---------+----------------------------------------- * g | Float | treat sizeof(float) characters as a * | | float in network byte order * -------+---------+----------------------------------------- * H | String | extract hex nibbles from each character * | | (most significant first) * -------+---------+----------------------------------------- * h | String | extract hex nibbles from each character * | | (least significant first) * -------+---------+----------------------------------------- * I | Integer | treat sizeof(int) (modified by _) * | | successive characters as an unsigned * | | native integer * -------+---------+----------------------------------------- * i | Integer | treat sizeof(int) (modified by _) * | | successive characters as a signed * | | native integer * -------+---------+----------------------------------------- * L | Integer | treat four (modified by _) successive * | | characters as an unsigned native * | | long integer * -------+---------+----------------------------------------- * l | Integer | treat four (modified by _) successive * | | characters as a signed native * | | long integer * -------+---------+----------------------------------------- * M | String | quoted-printable * -------+---------+----------------------------------------- * m | String | base64-encoded * -------+---------+----------------------------------------- * N | Integer | treat four characters as an unsigned * | | long in network byte order * -------+---------+----------------------------------------- * n | Fixnum | treat two characters as an unsigned * | | short in network byte order * -------+---------+----------------------------------------- * P | String | treat sizeof(char *) characters as a * | | pointer, and return \emph{len} characters * | | from the referenced location * -------+---------+----------------------------------------- * p | String | treat sizeof(char *) characters as a * | | pointer to a null-terminated string * -------+---------+----------------------------------------- * Q | Integer | treat 8 characters as an unsigned * | | quad word (64 bits) * -------+---------+----------------------------------------- * q | Integer | treat 8 characters as a signed * | | quad word (64 bits) * -------+---------+----------------------------------------- * S | Fixnum | treat two (different if _ used) * | | successive characters as an unsigned * | | short in native byte order * -------+---------+----------------------------------------- * s | Fixnum | Treat two (different if _ used) * | | successive characters as a signed short * | | in native byte order * -------+---------+----------------------------------------- * U | Integer | UTF-8 characters as unsigned integers * -------+---------+----------------------------------------- * u | String | UU-encoded * -------+---------+----------------------------------------- * V | Fixnum | treat four characters as an unsigned * | | long in little-endian byte order * -------+---------+----------------------------------------- * v | Fixnum | treat two characters as an unsigned * | | short in little-endian byte order * -------+---------+----------------------------------------- * w | Integer | BER-compressed integer (see Array.pack) * -------+---------+----------------------------------------- * X | --- | skip backward one character * -------+---------+----------------------------------------- * x | --- | skip forward one character * -------+---------+----------------------------------------- * Z | String | with trailing nulls removed * | | upto first null with * * -------+---------+----------------------------------------- * @ | --- | skip to the offset given by the * | | length argument * -------+---------+----------------------------------------- */ static VALUE pack_unpack(str, fmt) VALUE str, fmt; { static char *hexdigits = "0123456789abcdef0123456789ABCDEFx"; char *s, *send; char *p, *pend; VALUE ary; char type; long len; int tmp, star; #ifdef NATINT_PACK int natint; /* native integer */ #endif StringValue(str); StringValue(fmt); s = RSTRING(str)->ptr; send = s + RSTRING(str)->len; p = RSTRING(fmt)->ptr; pend = p + RSTRING(fmt)->len; ary = rb_ary_new(); while (p < pend) { type = *p++; #ifdef NATINT_PACK natint = 0; #endif if (ISSPACE(type)) continue; if (type == '#') { while ((p < pend) && (*p != '\n')) { p++; } continue; } star = 0; if (*p == '_' || *p == '!') { char *natstr = "sSiIlL"; if (strchr(natstr, type)) { #ifdef NATINT_PACK natint = 1; #endif p++; } else { rb_raise(rb_eArgError, "'%c' allowed only after types %s", *p, natstr); } } if (p >= pend) len = 1; else if (*p == '*') { star = 1; len = send - s; p++; } else if (ISDIGIT(*p)) { len = strtoul(p, (char**)&p, 10); } else { len = (type != '@'); } switch (type) { case '%': rb_raise(rb_eArgError, "%% is not supported"); break; case 'A': if (len > send - s) len = send - s; { long end = len; char *t = s + len - 1; while (t >= s) { if (*t != ' ' && *t != '\0') break; t--; len--; } rb_ary_push(ary, infected_str_new(s, len, str)); s += end; } break; case 'Z': { char *t = s; if (len > send-s) len = send-s; while (t < s+len && *t) t++; rb_ary_push(ary, infected_str_new(s, t-s, str)); if (t < send) t++; s = star ? t : s+len; } break; case 'a': if (len > send - s) len = send - s; rb_ary_push(ary, infected_str_new(s, len, str)); s += len; break; case 'b': { VALUE bitstr; char *t; int bits; long i; if (p[-1] == '*' || len > (send - s) * 8) len = (send - s) * 8; bits = 0; rb_ary_push(ary, bitstr = rb_str_new(0, len)); t = RSTRING(bitstr)->ptr; for (i=0; i>= 1; else bits = *s++; *t++ = (bits & 1) ? '1' : '0'; } } break; case 'B': { VALUE bitstr; char *t; int bits; long i; if (p[-1] == '*' || len > (send - s) * 8) len = (send - s) * 8; bits = 0; rb_ary_push(ary, bitstr = rb_str_new(0, len)); t = RSTRING(bitstr)->ptr; for (i=0; i (send - s) * 2) len = (send - s) * 2; bits = 0; rb_ary_push(ary, bitstr = rb_str_new(0, len)); t = RSTRING(bitstr)->ptr; for (i=0; i>= 4; else bits = *s++; *t++ = hexdigits[bits & 15]; } } break; case 'H': { VALUE bitstr; char *t; int bits; long i; if (p[-1] == '*' || len > (send - s) * 2) len = (send - s) * 2; bits = 0; rb_ary_push(ary, bitstr = rb_str_new(0, len)); t = RSTRING(bitstr)->ptr; for (i=0; i> 4) & 15]; } } break; case 'c': PACK_LENGTH_ADJUST(char,sizeof(char)); while (len-- > 0) { int c = *s++; if (c > (char)127) c-=256; rb_ary_push(ary, INT2FIX(c)); } PACK_ITEM_ADJUST(); break; case 'C': PACK_LENGTH_ADJUST(unsigned char,sizeof(unsigned char)); while (len-- > 0) { unsigned char c = *s++; rb_ary_push(ary, INT2FIX(c)); } PACK_ITEM_ADJUST(); break; case 's': PACK_LENGTH_ADJUST(short,2); while (len-- > 0) { short tmp = 0; memcpy(OFF16(&tmp), s, NATINT_LEN(short,2)); EXTEND16(tmp); s += NATINT_LEN(short,2); rb_ary_push(ary, INT2FIX(tmp)); } PACK_ITEM_ADJUST(); break; case 'S': PACK_LENGTH_ADJUST(unsigned short,2); while (len-- > 0) { unsigned short tmp = 0; memcpy(OFF16(&tmp), s, NATINT_LEN(unsigned short,2)); s += NATINT_LEN(unsigned short,2); rb_ary_push(ary, INT2FIX(tmp)); } PACK_ITEM_ADJUST(); break; case 'i': PACK_LENGTH_ADJUST(int,sizeof(int)); while (len-- > 0) { int tmp; memcpy(&tmp, s, sizeof(int)); s += sizeof(int); rb_ary_push(ary, INT2NUM(tmp)); } PACK_ITEM_ADJUST(); break; case 'I': PACK_LENGTH_ADJUST(unsigned int,sizeof(unsigned int)); while (len-- > 0) { unsigned int tmp; memcpy(&tmp, s, sizeof(unsigned int)); s += sizeof(unsigned int); rb_ary_push(ary, UINT2NUM(tmp)); } PACK_ITEM_ADJUST(); break; case 'l': PACK_LENGTH_ADJUST(long,4); while (len-- > 0) { long tmp = 0; memcpy(OFF32(&tmp), s, NATINT_LEN(long,4)); EXTEND32(tmp); s += NATINT_LEN(long,4); rb_ary_push(ary, LONG2NUM(tmp)); } PACK_ITEM_ADJUST(); break; case 'L': PACK_LENGTH_ADJUST(unsigned long,4); while (len-- > 0) { unsigned long tmp = 0; memcpy(OFF32(&tmp), s, NATINT_LEN(unsigned long,4)); s += NATINT_LEN(unsigned long,4); rb_ary_push(ary, ULONG2NUM(tmp)); } PACK_ITEM_ADJUST(); break; case 'q': PACK_LENGTH_ADJUST_SIZE(QUAD_SIZE); while (len-- > 0) { char *tmp = (char*)s; s += QUAD_SIZE; rb_ary_push(ary, rb_quad_unpack(tmp, 1)); } PACK_ITEM_ADJUST(); break; case 'Q': PACK_LENGTH_ADJUST_SIZE(QUAD_SIZE); while (len-- > 0) { char *tmp = (char*)s; s += QUAD_SIZE; rb_ary_push(ary, rb_quad_unpack(tmp, 0)); } break; case 'n': PACK_LENGTH_ADJUST(unsigned short,2); while (len-- > 0) { unsigned short tmp = 0; memcpy(OFF16B(&tmp), s, NATINT_LEN(unsigned short,2)); s += NATINT_LEN(unsigned short,2); rb_ary_push(ary, UINT2NUM(ntohs(tmp))); } PACK_ITEM_ADJUST(); break; case 'N': PACK_LENGTH_ADJUST(unsigned long,4); while (len-- > 0) { unsigned long tmp = 0; memcpy(OFF32B(&tmp), s, NATINT_LEN(unsigned long,4)); s += NATINT_LEN(unsigned long,4); rb_ary_push(ary, ULONG2NUM(ntohl(tmp))); } PACK_ITEM_ADJUST(); break; case 'v': PACK_LENGTH_ADJUST(unsigned short,2); while (len-- > 0) { unsigned short tmp = 0; memcpy(OFF16(&tmp), s, NATINT_LEN(unsigned short,2)); s += NATINT_LEN(unsigned short,2); rb_ary_push(ary, UINT2NUM(vtohs(tmp))); } PACK_ITEM_ADJUST(); break; case 'V': PACK_LENGTH_ADJUST(unsigned long,4); while (len-- > 0) { unsigned long tmp = 0; memcpy(OFF32(&tmp), s, NATINT_LEN(long,4)); s += NATINT_LEN(long,4); rb_ary_push(ary, ULONG2NUM(vtohl(tmp))); } PACK_ITEM_ADJUST(); break; case 'f': case 'F': PACK_LENGTH_ADJUST(float,sizeof(float)); while (len-- > 0) { float tmp; memcpy(&tmp, s, sizeof(float)); s += sizeof(float); rb_ary_push(ary, rb_float_new((double)tmp)); } PACK_ITEM_ADJUST(); break; case 'e': PACK_LENGTH_ADJUST(float,sizeof(float)); while (len-- > 0) { float tmp; FLOAT_CONVWITH(ftmp); memcpy(&tmp, s, sizeof(float)); s += sizeof(float); tmp = VTOHF(tmp,ftmp); rb_ary_push(ary, rb_float_new((double)tmp)); } PACK_ITEM_ADJUST(); break; case 'E': PACK_LENGTH_ADJUST(double,sizeof(double)); while (len-- > 0) { double tmp; DOUBLE_CONVWITH(dtmp); memcpy(&tmp, s, sizeof(double)); s += sizeof(double); tmp = VTOHD(tmp,dtmp); rb_ary_push(ary, rb_float_new(tmp)); } PACK_ITEM_ADJUST(); break; case 'D': case 'd': PACK_LENGTH_ADJUST(double,sizeof(double)); while (len-- > 0) { double tmp; memcpy(&tmp, s, sizeof(double)); s += sizeof(double); rb_ary_push(ary, rb_float_new(tmp)); } PACK_ITEM_ADJUST(); break; case 'g': PACK_LENGTH_ADJUST(float,sizeof(float)); while (len-- > 0) { float tmp; FLOAT_CONVWITH(ftmp;) memcpy(&tmp, s, sizeof(float)); s += sizeof(float); tmp = NTOHF(tmp,ftmp); rb_ary_push(ary, rb_float_new((double)tmp)); } PACK_ITEM_ADJUST(); break; case 'G': PACK_LENGTH_ADJUST(double,sizeof(double)); while (len-- > 0) { double tmp; DOUBLE_CONVWITH(dtmp); memcpy(&tmp, s, sizeof(double)); s += sizeof(double); tmp = NTOHD(tmp,dtmp); rb_ary_push(ary, rb_float_new(tmp)); } PACK_ITEM_ADJUST(); break; case 'U': if (len > send - s) len = send - s; while (len > 0 && s < send) { long alen = send - s; unsigned long l; l = utf8_to_uv(s, &alen); s += alen; len--; rb_ary_push(ary, ULONG2NUM(l)); } break; case 'u': { VALUE buf = infected_str_new(0, (send - s)*3/4, str); char *ptr = RSTRING(buf)->ptr; long total = 0; while (s < send && *s > ' ' && *s < 'a') { long a,b,c,d; char hunk[4]; hunk[3] = '\0'; len = (*s++ - ' ') & 077; total += len; if (total > RSTRING(buf)->len) { len -= total - RSTRING(buf)->len; total = RSTRING(buf)->len; } while (len > 0) { long mlen = len > 3 ? 3 : len; if (s < send && *s >= ' ') a = (*s++ - ' ') & 077; else a = 0; if (s < send && *s >= ' ') b = (*s++ - ' ') & 077; else b = 0; if (s < send && *s >= ' ') c = (*s++ - ' ') & 077; else c = 0; if (s < send && *s >= ' ') d = (*s++ - ' ') & 077; else d = 0; hunk[0] = a << 2 | b >> 4; hunk[1] = b << 4 | c >> 2; hunk[2] = c << 6 | d; memcpy(ptr, hunk, mlen); ptr += mlen; len -= mlen; } if (*s == '\r') s++; if (*s == '\n') s++; else if (s < send && (s+1 == send || s[1] == '\n')) s += 2; /* possible checksum byte */ } RSTRING(buf)->ptr[total] = '\0'; RSTRING(buf)->len = total; rb_ary_push(ary, buf); } break; case 'm': { VALUE buf = infected_str_new(0, (send - s)*3/4, str); char *ptr = RSTRING(buf)->ptr; int a = -1,b = -1,c = 0,d; static int first = 1; static int b64_xtable[256]; if (first) { int i; first = 0; for (i = 0; i < 256; i++) { b64_xtable[i] = -1; } for (i = 0; i < 64; i++) { b64_xtable[(int)b64_table[i]] = i; } } while (s < send) { a = b = c = d = -1; while((a = b64_xtable[(int)(*(unsigned char*)s)]) == -1 && s < send) { s++; } if( s >= send ) break; s++; while((b = b64_xtable[(int)(*(unsigned char*)s)]) == -1 && s < send) { s++; } if( s >= send ) break; s++; while((c = b64_xtable[(int)(*(unsigned char*)s)]) == -1 && s < send) { if( *s == '=' ) break; s++; } if( *s == '=' || s >= send ) break; s++; while((d = b64_xtable[(int)(*(unsigned char*)s)]) == -1 && s < send) { if( *s == '=' ) break; s++; } if( *s == '=' || s >= send ) break; s++; *ptr++ = a << 2 | b >> 4; *ptr++ = b << 4 | c >> 2; *ptr++ = c << 6 | d; } if (a != -1 && b != -1) { if (c == -1 && *s == '=') *ptr++ = a << 2 | b >> 4; else if (c != -1 && *s == '=') { *ptr++ = a << 2 | b >> 4; *ptr++ = b << 4 | c >> 2; } } *ptr = '\0'; RSTRING(buf)->len = ptr - RSTRING(buf)->ptr; rb_ary_push(ary, buf); } break; case 'M': { VALUE buf = infected_str_new(0, send - s, str); char *ptr = RSTRING(buf)->ptr; int c1, c2; while (s < send) { if (*s == '=') { if (++s == send) break; if (s+1 < send && *s == '\r' && *(s+1) == '\n') s++; if (*s != '\n') { if ((c1 = hex2num(*s)) == -1) break; if (++s == send) break; if ((c2 = hex2num(*s)) == -1) break; *ptr++ = c1 << 4 | c2; } } else { *ptr++ = *s; } s++; } *ptr = '\0'; RSTRING(buf)->len = ptr - RSTRING(buf)->ptr; rb_ary_push(ary, buf); } break; case '@': if (len > RSTRING(str)->len) rb_raise(rb_eArgError, "@ outside of string"); s = RSTRING(str)->ptr + len; break; case 'X': if (len > s - RSTRING(str)->ptr) rb_raise(rb_eArgError, "X outside of string"); s -= len; break; case 'x': if (len > send - s) rb_raise(rb_eArgError, "x outside of string"); s += len; break; case 'P': if (sizeof(char *) <= send - s) { char *t; VALUE tmp; memcpy(&t, s, sizeof(char *)); s += sizeof(char *); if (t) { VALUE a, *p, *pend; if (!(a = rb_str_associated(str))) { rb_raise(rb_eArgError, "no associated pointer"); } p = RARRAY(a)->ptr; pend = p + RARRAY(a)->len; while (p < pend) { if (TYPE(*p) == T_STRING && RSTRING(*p)->ptr == t) { if (len < RSTRING(*p)->len) { tmp = rb_tainted_str_new(t, len); rb_str_associate(tmp, a); } else { tmp = *p; } break; } p++; } if (p == pend) { rb_raise(rb_eArgError, "non associated pointer"); } } else { tmp = Qnil; } rb_ary_push(ary, tmp); } break; case 'p': if (len > (send - s) / sizeof(char *)) len = (send - s) / sizeof(char *); while (len-- > 0) { if (send - s < sizeof(char *)) break; else { VALUE tmp; char *t; memcpy(&t, s, sizeof(char *)); s += sizeof(char *); if (t) { VALUE a, *p, *pend; if (!(a = rb_str_associated(str))) { rb_raise(rb_eArgError, "no associated pointer"); } p = RARRAY(a)->ptr; pend = p + RARRAY(a)->len; while (p < pend) { if (TYPE(*p) == T_STRING && RSTRING(*p)->ptr == t) { tmp = *p; break; } p++; } if (p == pend) { rb_raise(rb_eArgError, "non associated pointer"); } } else { tmp = Qnil; } rb_ary_push(ary, tmp); } } break; case 'w': { unsigned long ul = 0; unsigned long ulmask = 0xfeUL << ((sizeof(unsigned long) - 1) * 8); while (len > 0 && s < send) { ul <<= 7; ul |= (*s & 0x7f); if (!(*s++ & 0x80)) { rb_ary_push(ary, ULONG2NUM(ul)); len--; ul = 0; } else if (ul & ulmask) { VALUE big = rb_uint2big(ul); VALUE big128 = rb_uint2big(128); while (s < send) { big = rb_big_mul(big, big128); big = rb_big_plus(big, rb_uint2big(*s & 0x7f)); if (!(*s++ & 0x80)) { rb_ary_push(ary, big); len--; ul = 0; break; } } } } } break; default: break; } } return ary; } #define BYTEWIDTH 8 static int uv_to_utf8(buf, uv) char *buf; unsigned long uv; { if (uv <= 0x7f) { buf[0] = (char)uv; return 1; } if (uv <= 0x7ff) { buf[0] = ((uv>>6)&0xff)|0xc0; buf[1] = (uv&0x3f)|0x80; return 2; } if (uv <= 0xffff) { buf[0] = ((uv>>12)&0xff)|0xe0; buf[1] = ((uv>>6)&0x3f)|0x80; buf[2] = (uv&0x3f)|0x80; return 3; } if (uv <= 0x1fffff) { buf[0] = ((uv>>18)&0xff)|0xf0; buf[1] = ((uv>>12)&0x3f)|0x80; buf[2] = ((uv>>6)&0x3f)|0x80; buf[3] = (uv&0x3f)|0x80; return 4; } if (uv <= 0x3ffffff) { buf[0] = ((uv>>24)&0xff)|0xf8; buf[1] = ((uv>>18)&0x3f)|0x80; buf[2] = ((uv>>12)&0x3f)|0x80; buf[3] = ((uv>>6)&0x3f)|0x80; buf[4] = (uv&0x3f)|0x80; return 5; } if (uv <= 0x7fffffff) { buf[0] = ((uv>>30)&0xff)|0xfc; buf[1] = ((uv>>24)&0x3f)|0x80; buf[2] = ((uv>>18)&0x3f)|0x80; buf[3] = ((uv>>12)&0x3f)|0x80; buf[4] = ((uv>>6)&0x3f)|0x80; buf[5] = (uv&0x3f)|0x80; return 6; } rb_raise(rb_eRangeError, "pack(U): value out of range"); } static const unsigned long utf8_limits[] = { 0x0, /* 1 */ 0x80, /* 2 */ 0x800, /* 3 */ 0x10000, /* 4 */ 0x200000, /* 5 */ 0x4000000, /* 6 */ 0x80000000, /* 7 */ }; static unsigned long utf8_to_uv(p, lenp) char *p; long *lenp; { int c = *p++ & 0xff; unsigned long uv = c; long n; if (!(uv & 0x80)) { *lenp = 1; return uv; } if (!(uv & 0x40)) { *lenp = 1; rb_raise(rb_eArgError, "malformed UTF-8 character"); } if (!(uv & 0x20)) { n = 2; uv &= 0x1f; } else if (!(uv & 0x10)) { n = 3; uv &= 0x0f; } else if (!(uv & 0x08)) { n = 4; uv &= 0x07; } else if (!(uv & 0x04)) { n = 5; uv &= 0x03; } else if (!(uv & 0x02)) { n = 6; uv &= 0x01; } else { *lenp = 1; rb_raise(rb_eArgError, "malformed UTF-8 character"); } if (n > *lenp) { rb_raise(rb_eArgError, "malformed UTF-8 character (expected %d bytes, given %d bytes)", n, *lenp); } *lenp = n--; if (n != 0) { while (n--) { c = *p++ & 0xff; if ((c & 0xc0) != 0x80) { *lenp -= n + 1; rb_raise(rb_eArgError, "malformed UTF-8 character"); } else { c &= 0x3f; uv = uv << 6 | c; } } } n = *lenp - 1; if (uv < utf8_limits[n]) { rb_raise(rb_eArgError, "redundant UTF-8 sequence"); } return uv; } void Init_pack() { rb_define_method(rb_cArray, "pack", pack_pack, 1); rb_define_method(rb_cString, "unpack", pack_unpack, 1); } /* A Bison parser, made by GNU Bison 2.3. */ /* Skeleton implementation for Bison's Yacc-like parsers in C Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* As a special exception, you may create a larger work that contains part or all of the Bison parser skeleton and distribute that work under terms of your choice, so long as that work isn't itself a parser generator using the skeleton or a modified version thereof as a parser skeleton. Alternatively, if you modify or redistribute the parser skeleton itself, you may (at your option) remove this special exception, which will cause the skeleton and the resulting Bison output files to be licensed under the GNU General Public License without this special exception. This special exception was added by the Free Software Foundation in version 2.2 of Bison. */ /* C LALR(1) parser skeleton written by Richard Stallman, by simplifying the original so-called "semantic" parser. */ /* All symbols defined below should begin with yy or YY, to avoid infringing on user name space. This should be done even for local variables, as they might otherwise be expanded by user macros. There are some unavoidable exceptions within include files to define necessary library symbols; they are noted "INFRINGES ON USER NAME SPACE" below. */ /* Identify Bison output. */ #define YYBISON 1 /* Bison version. */ #define YYBISON_VERSION "2.3" /* Skeleton name. */ #define YYSKELETON_NAME "yacc.c" /* Pure parsers. */ #define YYPURE 0 /* Using locations. */ #define YYLSP_NEEDED 0 /* Tokens. */ #ifndef YYTOKENTYPE # define YYTOKENTYPE /* Put the tokens into the symbol table, so that GDB and other debuggers know about them. */ enum yytokentype { kCLASS = 258, kMODULE = 259, kDEF = 260, kUNDEF = 261, kBEGIN = 262, kRESCUE = 263, kENSURE = 264, kEND = 265, kIF = 266, kUNLESS = 267, kTHEN = 268, kELSIF = 269, kELSE = 270, kCASE = 271, kWHEN = 272, kWHILE = 273, kUNTIL = 274, kFOR = 275, kBREAK = 276, kNEXT = 277, kREDO = 278, kRETRY = 279, kIN = 280, kDO = 281, kDO_COND = 282, kDO_BLOCK = 283, kRETURN = 284, kYIELD = 285, kSUPER = 286, kSELF = 287, kNIL = 288, kTRUE = 289, kFALSE = 290, kAND = 291, kOR = 292, kNOT = 293, kIF_MOD = 294, kUNLESS_MOD = 295, kWHILE_MOD = 296, kUNTIL_MOD = 297, kRESCUE_MOD = 298, kALIAS = 299, kDEFINED = 300, klBEGIN = 301, klEND = 302, k__LINE__ = 303, k__FILE__ = 304, tIDENTIFIER = 305, tFID = 306, tGVAR = 307, tIVAR = 308, tCONSTANT = 309, tCVAR = 310, tINTEGER = 311, tFLOAT = 312, tSTRING_CONTENT = 313, tNTH_REF = 314, tBACK_REF = 315, tREGEXP_END = 316, tUPLUS = 317, tUMINUS = 318, tPOW = 319, tCMP = 320, tEQ = 321, tEQQ = 322, tNEQ = 323, tGEQ = 324, tLEQ = 325, tANDOP = 326, tOROP = 327, tMATCH = 328, tNMATCH = 329, tDOT2 = 330, tDOT3 = 331, tAREF = 332, tASET = 333, tLSHFT = 334, tRSHFT = 335, tCOLON2 = 336, tCOLON3 = 337, tOP_ASGN = 338, tASSOC = 339, tLPAREN = 340, tLPAREN_ARG = 341, tRPAREN = 342, tLBRACK = 343, tLBRACE = 344, tLBRACE_ARG = 345, tSTAR = 346, tAMPER = 347, tSYMBEG = 348, tSTRING_BEG = 349, tXSTRING_BEG = 350, tREGEXP_BEG = 351, tWORDS_BEG = 352, tQWORDS_BEG = 353, tSTRING_DBEG = 354, tSTRING_DVAR = 355, tSTRING_END = 356, tLOWEST = 357, tUMINUS_NUM = 358, tLAST_TOKEN = 359 }; #endif /* Tokens. */ #define kCLASS 258 #define kMODULE 259 #define kDEF 260 #define kUNDEF 261 #define kBEGIN 262 #define kRESCUE 263 #define kENSURE 264 #define kEND 265 #define kIF 266 #define kUNLESS 267 #define kTHEN 268 #define kELSIF 269 #define kELSE 270 #define kCASE 271 #define kWHEN 272 #define kWHILE 273 #define kUNTIL 274 #define kFOR 275 #define kBREAK 276 #define kNEXT 277 #define kREDO 278 #define kRETRY 279 #define kIN 280 #define kDO 281 #define kDO_COND 282 #define kDO_BLOCK 283 #define kRETURN 284 #define kYIELD 285 #define kSUPER 286 #define kSELF 287 #define kNIL 288 #define kTRUE 289 #define kFALSE 290 #define kAND 291 #define kOR 292 #define kNOT 293 #define kIF_MOD 294 #define kUNLESS_MOD 295 #define kWHILE_MOD 296 #define kUNTIL_MOD 297 #define kRESCUE_MOD 298 #define kALIAS 299 #define kDEFINED 300 #define klBEGIN 301 #define klEND 302 #define k__LINE__ 303 #define k__FILE__ 304 #define tIDENTIFIER 305 #define tFID 306 #define tGVAR 307 #define tIVAR 308 #define tCONSTANT 309 #define tCVAR 310 #define tINTEGER 311 #define tFLOAT 312 #define tSTRING_CONTENT 313 #define tNTH_REF 314 #define tBACK_REF 315 #define tREGEXP_END 316 #define tUPLUS 317 #define tUMINUS 318 #define tPOW 319 #define tCMP 320 #define tEQ 321 #define tEQQ 322 #define tNEQ 323 #define tGEQ 324 #define tLEQ 325 #define tANDOP 326 #define tOROP 327 #define tMATCH 328 #define tNMATCH 329 #define tDOT2 330 #define tDOT3 331 #define tAREF 332 #define tASET 333 #define tLSHFT 334 #define tRSHFT 335 #define tCOLON2 336 #define tCOLON3 337 #define tOP_ASGN 338 #define tASSOC 339 #define tLPAREN 340 #define tLPAREN_ARG 341 #define tRPAREN 342 #define tLBRACK 343 #define tLBRACE 344 #define tLBRACE_ARG 345 #define tSTAR 346 #define tAMPER 347 #define tSYMBEG 348 #define tSTRING_BEG 349 #define tXSTRING_BEG 350 #define tREGEXP_BEG 351 #define tWORDS_BEG 352 #define tQWORDS_BEG 353 #define tSTRING_DBEG 354 #define tSTRING_DVAR 355 #define tSTRING_END 356 #define tLOWEST 357 #define tUMINUS_NUM 358 #define tLAST_TOKEN 359 /* Copy the first part of user declarations. */ #line 13 "parse.y" #define YYDEBUG 1 #define YYERROR_VERBOSE 1 #ifndef YYSTACK_USE_ALLOCA #define YYSTACK_USE_ALLOCA 0 #endif #include "ruby.h" #include "env.h" #include "intern.h" #include "node.h" #include "st.h" #include #include #include #define YYMALLOC rb_parser_malloc #define YYREALLOC rb_parser_realloc #define YYCALLOC rb_parser_calloc #define YYFREE rb_parser_free #define malloc YYMALLOC #define realloc YYREALLOC #define calloc YYCALLOC #define free YYFREE static void *rb_parser_malloc _((size_t)); static void *rb_parser_realloc _((void *, size_t)); static void *rb_parser_calloc _((size_t, size_t)); static void rb_parser_free _((void *)); #define yyparse ruby_yyparse #define yylex ruby_yylex #define yyerror ruby_yyerror #define yylval ruby_yylval #define yychar ruby_yychar #define yydebug ruby_yydebug #define ID_SCOPE_SHIFT 3 #define ID_SCOPE_MASK 0x07 #define ID_LOCAL 0x01 #define ID_INSTANCE 0x02 #define ID_GLOBAL 0x03 #define ID_ATTRSET 0x04 #define ID_CONST 0x05 #define ID_CLASS 0x06 #define ID_JUNK 0x07 #define ID_INTERNAL ID_JUNK #define is_notop_id(id) ((id)>tLAST_TOKEN) #define is_local_id(id) (is_notop_id(id)&&((id)&ID_SCOPE_MASK)==ID_LOCAL) #define is_global_id(id) (is_notop_id(id)&&((id)&ID_SCOPE_MASK)==ID_GLOBAL) #define is_instance_id(id) (is_notop_id(id)&&((id)&ID_SCOPE_MASK)==ID_INSTANCE) #define is_attrset_id(id) (is_notop_id(id)&&((id)&ID_SCOPE_MASK)==ID_ATTRSET) #define is_const_id(id) (is_notop_id(id)&&((id)&ID_SCOPE_MASK)==ID_CONST) #define is_class_id(id) (is_notop_id(id)&&((id)&ID_SCOPE_MASK)==ID_CLASS) #define is_junk_id(id) (is_notop_id(id)&&((id)&ID_SCOPE_MASK)==ID_JUNK) #define is_asgn_or_id(id) ((is_notop_id(id)) && \ (((id)&ID_SCOPE_MASK) == ID_GLOBAL || \ ((id)&ID_SCOPE_MASK) == ID_INSTANCE || \ ((id)&ID_SCOPE_MASK) == ID_CLASS)) NODE *ruby_eval_tree_begin = 0; NODE *ruby_eval_tree = 0; char *ruby_sourcefile; /* current source file */ int ruby_sourceline; /* current line no. */ static int yylex(); static int yyerror(); static enum lex_state { EXPR_BEG, /* ignore newline, +/- is a sign. */ EXPR_END, /* newline significant, +/- is an operator. */ EXPR_ARG, /* newline significant, +/- is an operator. */ EXPR_CMDARG, /* newline significant, +/- is an operator. */ EXPR_ENDARG, /* newline significant, +/- is an operator. */ EXPR_MID, /* newline significant, +/- is an operator. */ EXPR_FNAME, /* ignore newline, no reserved words. */ EXPR_DOT, /* right after `.' or `::', no reserved words. */ EXPR_CLASS, /* immediate after `class', no here document. */ } lex_state; static NODE *lex_strterm; #ifdef HAVE_LONG_LONG typedef unsigned LONG_LONG stack_type; #else typedef unsigned long stack_type; #endif #define BITSTACK_PUSH(stack, n) (stack = (stack<<1)|((n)&1)) #define BITSTACK_POP(stack) (stack >>= 1) #define BITSTACK_LEXPOP(stack) (stack = (stack >> 1) | (stack & 1)) #define BITSTACK_SET_P(stack) (stack&1) static stack_type cond_stack = 0; #define COND_PUSH(n) BITSTACK_PUSH(cond_stack, n) #define COND_POP() BITSTACK_POP(cond_stack) #define COND_LEXPOP() BITSTACK_LEXPOP(cond_stack) #define COND_P() BITSTACK_SET_P(cond_stack) static stack_type cmdarg_stack = 0; #define CMDARG_PUSH(n) BITSTACK_PUSH(cmdarg_stack, n) #define CMDARG_POP() BITSTACK_POP(cmdarg_stack) #define CMDARG_LEXPOP() BITSTACK_LEXPOP(cmdarg_stack) #define CMDARG_P() BITSTACK_SET_P(cmdarg_stack) static int class_nest = 0; static int in_single = 0; static int in_def = 0; static int compile_for_eval = 0; static ID cur_mid = 0; static int command_start = Qtrue; static NODE *deferred_nodes; static NODE *cond(); static NODE *logop(); static int cond_negative(); static NODE *newline_node(); static void fixpos(); static int value_expr0(); static void void_expr0(); static void void_stmts(); static NODE *remove_begin(); #define value_expr(node) value_expr0((node) = remove_begin(node)) #define void_expr(node) void_expr0((node) = remove_begin(node)) static NODE *block_append(); static NODE *list_append(); static NODE *list_concat(); static NODE *arg_concat(); static NODE *arg_prepend(); static NODE *literal_concat(); static NODE *new_evstr(); static NODE *evstr2dstr(); static NODE *call_op(); static int in_defined = 0; static NODE *negate_lit(); static NODE *ret_args(); static NODE *arg_blk_pass(); static NODE *new_call(); static NODE *new_fcall(); static NODE *new_super(); static NODE *new_yield(); static NODE *gettable(); static NODE *assignable(); static NODE *aryset(); static NODE *attrset(); static void rb_backref_error(); static NODE *node_assign(); static NODE *match_gen(); static void local_push(); static void local_pop(); static int local_append(); static int local_cnt(); static int local_id(); static ID *local_tbl(); static ID internal_id(); static struct RVarmap *dyna_push(); static void dyna_pop(); static int dyna_in_block(); static NODE *dyna_init(); static void top_local_init(); static void top_local_setup(); static void fixup_nodes(); #define RE_OPTION_ONCE 0x80 #define NODE_STRTERM NODE_ZARRAY /* nothing to gc */ #define NODE_HEREDOC NODE_ARRAY /* 1, 3 to gc */ #define SIGN_EXTEND(x,n) (((1<<(n)-1)^((x)&~(~0<<(n))))-(1<<(n)-1)) #define nd_func u1.id #if SIZEOF_SHORT == 2 #define nd_term(node) ((signed short)(node)->u2.id) #else #define nd_term(node) SIGN_EXTEND((node)->u2.id, CHAR_BIT*2) #endif #define nd_paren(node) (char)((node)->u2.id >> CHAR_BIT*2) #define nd_nest u3.id /* Older versions of Yacc set YYMAXDEPTH to a very low value by default (150, for instance). This is too low for Ruby to parse some files, such as date/format.rb, therefore bump the value up to at least Bison's default. */ #ifdef OLD_YACC #ifndef YYMAXDEPTH #define YYMAXDEPTH 10000 #endif #endif /* Enabling traces. */ #ifndef YYDEBUG # define YYDEBUG 0 #endif /* Enabling verbose error messages. */ #ifdef YYERROR_VERBOSE # undef YYERROR_VERBOSE # define YYERROR_VERBOSE 1 #else # define YYERROR_VERBOSE 0 #endif /* Enabling the token table. */ #ifndef YYTOKEN_TABLE # define YYTOKEN_TABLE 0 #endif #if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED typedef union YYSTYPE #line 213 "parse.y" { NODE *node; ID id; int num; struct RVarmap *vars; } /* Line 193 of yacc.c. */ #line 511 "parse.c" YYSTYPE; # define yystype YYSTYPE /* obsolescent; will be withdrawn */ # define YYSTYPE_IS_DECLARED 1 # define YYSTYPE_IS_TRIVIAL 1 #endif /* Copy the second part of user declarations. */ /* Line 216 of yacc.c. */ #line 524 "parse.c" #ifdef short # undef short #endif #ifdef YYTYPE_UINT8 typedef YYTYPE_UINT8 yytype_uint8; #else typedef unsigned char yytype_uint8; #endif #ifdef YYTYPE_INT8 typedef YYTYPE_INT8 yytype_int8; #elif (defined __STDC__ || defined __C99__FUNC__ \ || defined __cplusplus || defined _MSC_VER) typedef signed char yytype_int8; #else typedef short int yytype_int8; #endif #ifdef YYTYPE_UINT16 typedef YYTYPE_UINT16 yytype_uint16; #else typedef unsigned short int yytype_uint16; #endif #ifdef YYTYPE_INT16 typedef YYTYPE_INT16 yytype_int16; #else typedef short int yytype_int16; #endif #ifndef YYSIZE_T # ifdef __SIZE_TYPE__ # define YYSIZE_T __SIZE_TYPE__ # elif defined size_t # define YYSIZE_T size_t # elif ! defined YYSIZE_T && (defined __STDC__ || defined __C99__FUNC__ \ || defined __cplusplus || defined _MSC_VER) # include /* INFRINGES ON USER NAME SPACE */ # define YYSIZE_T size_t # else # define YYSIZE_T unsigned int # endif #endif #define YYSIZE_MAXIMUM ((YYSIZE_T) -1) #ifndef YY_ # if YYENABLE_NLS # if ENABLE_NLS # include /* INFRINGES ON USER NAME SPACE */ # define YY_(msgid) dgettext ("bison-runtime", msgid) # endif # endif # ifndef YY_ # define YY_(msgid) msgid # endif #endif /* Suppress unused-variable warnings by "using" E. */ #if ! defined lint || defined __GNUC__ # define YYUSE(e) ((void) (e)) #else # define YYUSE(e) /* empty */ #endif /* Identity function, used to suppress warnings about constant conditions. */ #ifndef lint # define YYID(n) (n) #else #if (defined __STDC__ || defined __C99__FUNC__ \ || defined __cplusplus || defined _MSC_VER) static int YYID (int i) #else static int YYID (i) int i; #endif { return i; } #endif #if ! defined yyoverflow || YYERROR_VERBOSE /* The parser invokes alloca or malloc; define the necessary symbols. */ # ifdef YYSTACK_USE_ALLOCA # if YYSTACK_USE_ALLOCA # ifdef __GNUC__ # define YYSTACK_ALLOC __builtin_alloca # elif defined __BUILTIN_VA_ARG_INCR # include /* INFRINGES ON USER NAME SPACE */ # elif defined _AIX # define YYSTACK_ALLOC __alloca # elif defined _MSC_VER # include /* INFRINGES ON USER NAME SPACE */ # define alloca _alloca # else # define YYSTACK_ALLOC alloca # if ! defined _ALLOCA_H && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ || defined __cplusplus || defined _MSC_VER) # include /* INFRINGES ON USER NAME SPACE */ # ifndef _STDLIB_H # define _STDLIB_H 1 # endif # endif # endif # endif # endif # ifdef YYSTACK_ALLOC /* Pacify GCC's `empty if-body' warning. */ # define YYSTACK_FREE(Ptr) do { /* empty */; } while (YYID (0)) # ifndef YYSTACK_ALLOC_MAXIMUM /* The OS might guarantee only one guard page at the bottom of the stack, and a page size can be as small as 4096 bytes. So we cannot safely invoke alloca (N) if N exceeds 4096. Use a slightly smaller number to allow for a few compiler-allocated temporary stack slots. */ # define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ # endif # else # define YYSTACK_ALLOC YYMALLOC # define YYSTACK_FREE YYFREE # ifndef YYSTACK_ALLOC_MAXIMUM # define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM # endif # if (defined __cplusplus && ! defined _STDLIB_H \ && ! ((defined YYMALLOC || defined malloc) \ && (defined YYFREE || defined free))) # include /* INFRINGES ON USER NAME SPACE */ # ifndef _STDLIB_H # define _STDLIB_H 1 # endif # endif # ifndef YYMALLOC # define YYMALLOC malloc # if ! defined malloc && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ || defined __cplusplus || defined _MSC_VER) void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ # endif # endif # ifndef YYFREE # define YYFREE free # if ! defined free && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \ || defined __cplusplus || defined _MSC_VER) void free (void *); /* INFRINGES ON USER NAME SPACE */ # endif # endif # endif #endif /* ! defined yyoverflow || YYERROR_VERBOSE */ #if (! defined yyoverflow \ && (! defined __cplusplus \ || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) /* A type that is properly aligned for any stack member. */ union yyalloc { yytype_int16 yyss; YYSTYPE yyvs; }; /* The size of the maximum gap between one aligned stack and the next. */ # define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1) /* The size of an array large to enough to hold all stacks, each with N elements. */ # define YYSTACK_BYTES(N) \ ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE)) \ + YYSTACK_GAP_MAXIMUM) /* Copy COUNT objects from FROM to TO. The source and destination do not overlap. */ # ifndef YYCOPY # if defined __GNUC__ && 1 < __GNUC__ # define YYCOPY(To, From, Count) \ __builtin_memcpy (To, From, (Count) * sizeof (*(From))) # else # define YYCOPY(To, From, Count) \ do \ { \ YYSIZE_T yyi; \ for (yyi = 0; yyi < (Count); yyi++) \ (To)[yyi] = (From)[yyi]; \ } \ while (YYID (0)) # endif # endif /* Relocate STACK from its old location to the new one. The local variables YYSIZE and YYSTACKSIZE give the old and new number of elements in the stack, and YYPTR gives the new location of the stack. Advance YYPTR to a properly aligned location for the next stack. */ # define YYSTACK_RELOCATE(Stack) \ do \ { \ YYSIZE_T yynewbytes; \ YYCOPY (&yyptr->Stack, Stack, yysize); \ Stack = &yyptr->Stack; \ yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \ yyptr += yynewbytes / sizeof (*yyptr); \ } \ while (YYID (0)) #endif /* YYFINAL -- State number of the termination state. */ #define YYFINAL 3 /* YYLAST -- Last index in YYTABLE. */ #define YYLAST 9550 /* YYNTOKENS -- Number of terminals. */ #define YYNTOKENS 132 /* YYNNTS -- Number of nonterminals. */ #define YYNNTS 144 /* YYNRULES -- Number of rules. */ #define YYNRULES 501 /* YYNRULES -- Number of states. */ #define YYNSTATES 895 /* YYTRANSLATE(YYLEX) -- Bison symbol number corresponding to YYLEX. */ #define YYUNDEFTOK 2 #define YYMAXUTOK 359 #define YYTRANSLATE(YYX) \ ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) /* YYTRANSLATE[YYLEX] -- Bison symbol number corresponding to YYLEX. */ static const yytype_uint8 yytranslate[] = { 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 130, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 129, 117, 2, 2, 2, 115, 110, 2, 128, 125, 113, 111, 126, 112, 124, 114, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 105, 131, 107, 103, 106, 104, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 122, 2, 123, 109, 2, 127, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 120, 108, 121, 118, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 116, 119 }; #if YYDEBUG /* YYPRHS[YYN] -- Index of the first RHS symbol of rule number YYN in YYRHS. */ static const yytype_uint16 yyprhs[] = { 0, 0, 3, 4, 7, 12, 15, 17, 19, 23, 26, 27, 32, 36, 40, 44, 47, 51, 55, 59, 63, 67, 68, 74, 79, 83, 87, 91, 98, 104, 110, 116, 120, 124, 128, 132, 134, 136, 140, 144, 147, 150, 152, 154, 156, 158, 161, 164, 167, 169, 174, 179, 180, 181, 188, 191, 195, 200, 206, 211, 217, 220, 223, 225, 229, 231, 235, 237, 240, 244, 247, 250, 252, 254, 258, 261, 265, 267, 272, 276, 280, 284, 288, 291, 293, 295, 300, 304, 308, 312, 316, 319, 321, 323, 325, 328, 330, 334, 336, 338, 340, 342, 344, 346, 348, 350, 352, 354, 355, 360, 362, 364, 366, 368, 370, 372, 374, 376, 378, 380, 382, 384, 386, 388, 390, 392, 394, 396, 398, 400, 402, 404, 406, 408, 410, 412, 414, 416, 418, 420, 422, 424, 426, 428, 430, 432, 434, 436, 438, 440, 442, 444, 446, 448, 450, 452, 454, 456, 458, 460, 462, 464, 466, 468, 470, 472, 474, 476, 478, 480, 482, 484, 486, 488, 490, 492, 496, 502, 506, 513, 519, 525, 531, 537, 542, 546, 550, 554, 558, 562, 566, 570, 574, 578, 583, 588, 591, 594, 598, 602, 606, 610, 614, 618, 622, 626, 630, 634, 638, 642, 646, 649, 652, 656, 660, 664, 668, 669, 674, 680, 682, 684, 686, 689, 692, 698, 701, 705, 709, 714, 719, 726, 728, 730, 732, 735, 741, 744, 750, 755, 763, 767, 769, 774, 778, 784, 792, 795, 801, 806, 813, 821, 831, 835, 837, 838, 841, 843, 844, 848, 849, 854, 857, 860, 862, 864, 868, 872, 877, 880, 882, 884, 886, 888, 890, 892, 894, 896, 898, 899, 904, 905, 911, 915, 919, 922, 927, 931, 935, 937, 942, 946, 948, 949, 956, 959, 961, 964, 971, 978, 979, 980, 988, 989, 990, 998, 1004, 1009, 1015, 1016, 1017, 1027, 1028, 1035, 1036, 1037, 1046, 1047, 1053, 1054, 1061, 1062, 1063, 1073, 1075, 1077, 1079, 1081, 1083, 1085, 1087, 1089, 1092, 1094, 1096, 1098, 1100, 1106, 1108, 1111, 1113, 1115, 1117, 1120, 1122, 1126, 1127, 1128, 1135, 1138, 1143, 1148, 1151, 1156, 1161, 1165, 1168, 1170, 1171, 1172, 1179, 1180, 1181, 1188, 1194, 1196, 1201, 1204, 1206, 1208, 1215, 1217, 1219, 1221, 1223, 1226, 1228, 1231, 1233, 1235, 1237, 1239, 1241, 1243, 1246, 1250, 1254, 1258, 1262, 1266, 1267, 1271, 1273, 1276, 1280, 1284, 1285, 1289, 1290, 1293, 1294, 1297, 1299, 1300, 1304, 1305, 1310, 1312, 1314, 1316, 1318, 1321, 1323, 1325, 1327, 1329, 1333, 1335, 1337, 1340, 1343, 1345, 1347, 1349, 1351, 1353, 1355, 1357, 1359, 1361, 1363, 1365, 1367, 1369, 1371, 1373, 1375, 1376, 1381, 1384, 1389, 1392, 1399, 1404, 1409, 1412, 1417, 1420, 1423, 1425, 1426, 1428, 1430, 1432, 1434, 1436, 1438, 1442, 1446, 1448, 1452, 1454, 1456, 1459, 1461, 1463, 1465, 1468, 1471, 1473, 1475, 1476, 1482, 1484, 1487, 1490, 1492, 1496, 1500, 1502, 1504, 1506, 1508, 1510, 1512, 1514, 1516, 1518, 1520, 1522, 1524, 1525, 1527, 1528, 1530, 1531, 1533, 1535, 1537, 1539, 1541, 1544 }; /* YYRHS -- A `-1'-separated list of the rules' RHS. */ static const yytype_int16 yyrhs[] = { 133, 0, -1, -1, 134, 136, -1, 136, 219, 203, 222, -1, 137, 270, -1, 275, -1, 138, -1, 137, 274, 138, -1, 1, 138, -1, -1, 44, 160, 139, 160, -1, 44, 52, 52, -1, 44, 52, 60, -1, 44, 52, 59, -1, 6, 161, -1, 138, 39, 142, -1, 138, 40, 142, -1, 138, 41, 142, -1, 138, 42, 142, -1, 138, 43, 138, -1, -1, 46, 140, 120, 136, 121, -1, 47, 120, 136, 121, -1, 155, 103, 143, -1, 149, 103, 143, -1, 246, 83, 143, -1, 199, 122, 168, 123, 83, 143, -1, 199, 124, 50, 83, 143, -1, 199, 124, 54, 83, 143, -1, 199, 81, 50, 83, 143, -1, 247, 83, 143, -1, 155, 103, 181, -1, 149, 103, 167, -1, 149, 103, 181, -1, 141, -1, 143, -1, 141, 36, 141, -1, 141, 37, 141, -1, 38, 141, -1, 117, 143, -1, 165, -1, 141, -1, 148, -1, 144, -1, 29, 171, -1, 21, 171, -1, 22, 171, -1, 209, -1, 209, 124, 267, 173, -1, 209, 81, 267, 173, -1, -1, -1, 90, 146, 205, 147, 136, 121, -1, 266, 173, -1, 266, 173, 145, -1, 199, 124, 267, 173, -1, 199, 124, 267, 173, 145, -1, 199, 81, 267, 173, -1, 199, 81, 267, 173, 145, -1, 31, 173, -1, 30, 173, -1, 151, -1, 85, 150, 125, -1, 151, -1, 85, 150, 125, -1, 153, -1, 153, 152, -1, 153, 91, 154, -1, 153, 91, -1, 91, 154, -1, 91, -1, 154, -1, 85, 150, 125, -1, 152, 126, -1, 153, 152, 126, -1, 244, -1, 199, 122, 168, 123, -1, 199, 124, 50, -1, 199, 81, 50, -1, 199, 124, 54, -1, 199, 81, 54, -1, 82, 54, -1, 247, -1, 244, -1, 199, 122, 168, 123, -1, 199, 124, 50, -1, 199, 81, 50, -1, 199, 124, 54, -1, 199, 81, 54, -1, 82, 54, -1, 247, -1, 50, -1, 54, -1, 82, 156, -1, 156, -1, 199, 81, 156, -1, 50, -1, 54, -1, 51, -1, 163, -1, 164, -1, 158, -1, 240, -1, 159, -1, 242, -1, 160, -1, -1, 161, 126, 162, 160, -1, 108, -1, 109, -1, 110, -1, 65, -1, 66, -1, 67, -1, 73, -1, 106, -1, 69, -1, 107, -1, 70, -1, 79, -1, 80, -1, 111, -1, 112, -1, 113, -1, 91, -1, 114, -1, 115, -1, 64, -1, 118, -1, 62, -1, 63, -1, 77, -1, 78, -1, 127, -1, 48, -1, 49, -1, 46, -1, 47, -1, 44, -1, 36, -1, 7, -1, 21, -1, 16, -1, 3, -1, 5, -1, 45, -1, 26, -1, 15, -1, 14, -1, 10, -1, 9, -1, 35, -1, 20, -1, 25, -1, 4, -1, 22, -1, 33, -1, 38, -1, 37, -1, 23, -1, 8, -1, 24, -1, 29, -1, 32, -1, 31, -1, 13, -1, 34, -1, 6, -1, 17, -1, 30, -1, 11, -1, 12, -1, 18, -1, 19, -1, 155, 103, 165, -1, 155, 103, 165, 43, 165, -1, 246, 83, 165, -1, 199, 122, 168, 123, 83, 165, -1, 199, 124, 50, 83, 165, -1, 199, 124, 54, 83, 165, -1, 199, 81, 50, 83, 165, -1, 199, 81, 54, 83, 165, -1, 82, 54, 83, 165, -1, 247, 83, 165, -1, 165, 75, 165, -1, 165, 76, 165, -1, 165, 111, 165, -1, 165, 112, 165, -1, 165, 113, 165, -1, 165, 114, 165, -1, 165, 115, 165, -1, 165, 64, 165, -1, 116, 56, 64, 165, -1, 116, 57, 64, 165, -1, 62, 165, -1, 63, 165, -1, 165, 108, 165, -1, 165, 109, 165, -1, 165, 110, 165, -1, 165, 65, 165, -1, 165, 106, 165, -1, 165, 69, 165, -1, 165, 107, 165, -1, 165, 70, 165, -1, 165, 66, 165, -1, 165, 67, 165, -1, 165, 68, 165, -1, 165, 73, 165, -1, 165, 74, 165, -1, 117, 165, -1, 118, 165, -1, 165, 79, 165, -1, 165, 80, 165, -1, 165, 71, 165, -1, 165, 72, 165, -1, -1, 45, 271, 166, 165, -1, 165, 104, 165, 105, 165, -1, 182, -1, 165, -1, 275, -1, 148, 271, -1, 180, 272, -1, 180, 126, 91, 165, 271, -1, 264, 272, -1, 91, 165, 271, -1, 128, 275, 125, -1, 128, 171, 271, 125, -1, 128, 209, 271, 125, -1, 128, 180, 126, 209, 271, 125, -1, 275, -1, 169, -1, 148, -1, 180, 179, -1, 180, 126, 91, 167, 179, -1, 264, 179, -1, 264, 126, 91, 167, 179, -1, 180, 126, 264, 179, -1, 180, 126, 264, 126, 91, 165, 179, -1, 91, 167, 179, -1, 178, -1, 167, 126, 180, 179, -1, 167, 126, 178, -1, 167, 126, 91, 167, 179, -1, 167, 126, 180, 126, 91, 167, 179, -1, 264, 179, -1, 264, 126, 91, 167, 179, -1, 167, 126, 264, 179, -1, 167, 126, 180, 126, 264, 179, -1, 167, 126, 264, 126, 91, 167, 179, -1, 167, 126, 180, 126, 264, 126, 91, 167, 179, -1, 91, 167, 179, -1, 178, -1, -1, 174, 175, -1, 171, -1, -1, 86, 176, 125, -1, -1, 86, 172, 177, 125, -1, 92, 167, -1, 126, 178, -1, 275, -1, 167, -1, 180, 126, 167, -1, 180, 126, 167, -1, 180, 126, 91, 167, -1, 91, 167, -1, 223, -1, 224, -1, 227, -1, 228, -1, 229, -1, 232, -1, 245, -1, 247, -1, 51, -1, -1, 7, 183, 135, 10, -1, -1, 86, 141, 184, 271, 125, -1, 85, 136, 125, -1, 199, 81, 54, -1, 82, 54, -1, 199, 122, 168, 123, -1, 88, 168, 123, -1, 89, 263, 121, -1, 29, -1, 30, 128, 171, 125, -1, 30, 128, 125, -1, 30, -1, -1, 45, 271, 128, 185, 141, 125, -1, 266, 211, -1, 210, -1, 210, 211, -1, 11, 142, 200, 136, 202, 10, -1, 12, 142, 200, 136, 203, 10, -1, -1, -1, 18, 186, 142, 201, 187, 136, 10, -1, -1, -1, 19, 188, 142, 201, 189, 136, 10, -1, 16, 142, 270, 216, 10, -1, 16, 270, 216, 10, -1, 16, 270, 15, 136, 10, -1, -1, -1, 20, 204, 25, 190, 142, 201, 191, 136, 10, -1, -1, 3, 157, 248, 192, 135, 10, -1, -1, -1, 3, 79, 141, 193, 273, 194, 135, 10, -1, -1, 4, 157, 195, 135, 10, -1, -1, 5, 158, 196, 250, 135, 10, -1, -1, -1, 5, 261, 269, 197, 158, 198, 250, 135, 10, -1, 21, -1, 22, -1, 23, -1, 24, -1, 182, -1, 273, -1, 105, -1, 13, -1, 273, 13, -1, 273, -1, 105, -1, 27, -1, 203, -1, 14, 142, 200, 136, 202, -1, 275, -1, 15, 136, -1, 155, -1, 149, -1, 275, -1, 108, 108, -1, 72, -1, 108, 204, 108, -1, -1, -1, 28, 207, 205, 208, 136, 10, -1, 148, 206, -1, 209, 124, 267, 170, -1, 209, 81, 267, 170, -1, 266, 169, -1, 199, 124, 267, 170, -1, 199, 81, 267, 169, -1, 199, 81, 268, -1, 31, 169, -1, 31, -1, -1, -1, 120, 212, 205, 213, 136, 121, -1, -1, -1, 26, 214, 205, 215, 136, 10, -1, 17, 217, 200, 136, 218, -1, 180, -1, 180, 126, 91, 167, -1, 91, 167, -1, 203, -1, 216, -1, 8, 220, 221, 200, 136, 219, -1, 275, -1, 167, -1, 181, -1, 275, -1, 84, 155, -1, 275, -1, 9, 136, -1, 275, -1, 243, -1, 240, -1, 242, -1, 225, -1, 226, -1, 225, 226, -1, 94, 234, 101, -1, 95, 235, 101, -1, 96, 235, 61, -1, 97, 129, 101, -1, 97, 230, 101, -1, -1, 230, 231, 129, -1, 236, -1, 231, 236, -1, 98, 129, 101, -1, 98, 233, 101, -1, -1, 233, 58, 129, -1, -1, 234, 236, -1, -1, 235, 236, -1, 58, -1, -1, 100, 237, 239, -1, -1, 99, 238, 136, 121, -1, 52, -1, 53, -1, 55, -1, 247, -1, 93, 241, -1, 158, -1, 53, -1, 52, -1, 55, -1, 93, 235, 101, -1, 56, -1, 57, -1, 116, 56, -1, 116, 57, -1, 50, -1, 53, -1, 52, -1, 54, -1, 55, -1, 33, -1, 32, -1, 34, -1, 35, -1, 49, -1, 48, -1, 244, -1, 244, -1, 59, -1, 60, -1, 273, -1, -1, 107, 249, 142, 273, -1, 1, 273, -1, 128, 251, 271, 125, -1, 251, 273, -1, 253, 126, 255, 126, 257, 260, -1, 253, 126, 255, 260, -1, 253, 126, 257, 260, -1, 253, 260, -1, 255, 126, 257, 260, -1, 255, 260, -1, 257, 260, -1, 259, -1, -1, 54, -1, 53, -1, 52, -1, 55, -1, 50, -1, 252, -1, 253, 126, 252, -1, 50, 103, 167, -1, 254, -1, 255, 126, 254, -1, 113, -1, 91, -1, 256, 50, -1, 256, -1, 110, -1, 92, -1, 258, 50, -1, 126, 259, -1, 275, -1, 245, -1, -1, 128, 262, 141, 271, 125, -1, 275, -1, 264, 272, -1, 180, 272, -1, 265, -1, 264, 126, 265, -1, 167, 84, 167, -1, 50, -1, 54, -1, 51, -1, 50, -1, 54, -1, 51, -1, 163, -1, 50, -1, 51, -1, 163, -1, 124, -1, 81, -1, -1, 274, -1, -1, 130, -1, -1, 130, -1, 126, -1, 131, -1, 130, -1, 273, -1, 274, 131, -1, -1 }; /* YYRLINE[YYN] -- source line where rule number YYN was defined. */ static const yytype_uint16 yyrline[] = { 0, 350, 350, 350, 375, 395, 403, 404, 408, 412, 418, 418, 422, 426, 433, 438, 442, 451, 460, 472, 484, 490, 489, 503, 511, 515, 521, 546, 562, 574, 586, 598, 603, 607, 612, 617, 620, 621, 625, 629, 633, 637, 640, 647, 648, 649, 653, 657, 663, 664, 668, 675, 679, 674, 689, 694, 706, 711, 723, 728, 740, 745, 752, 753, 759, 760, 766, 770, 774, 778, 782, 786, 792, 793, 799, 803, 809, 813, 817, 821, 825, 829, 835, 841, 848, 852, 856, 860, 864, 868, 874, 880, 887, 891, 894, 898, 902, 908, 909, 910, 911, 916, 923, 924, 927, 931, 934, 938, 938, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 972, 972, 972, 972, 973, 973, 973, 973, 973, 973, 973, 974, 974, 974, 974, 974, 974, 974, 975, 975, 975, 975, 975, 975, 976, 976, 976, 976, 976, 976, 976, 977, 977, 977, 977, 977, 978, 978, 978, 978, 981, 985, 989, 1014, 1030, 1042, 1054, 1066, 1071, 1076, 1081, 1091, 1101, 1105, 1109, 1113, 1117, 1121, 1125, 1129, 1133, 1142, 1146, 1150, 1154, 1158, 1162, 1166, 1170, 1174, 1178, 1182, 1186, 1190, 1194, 1198, 1202, 1206, 1210, 1214, 1218, 1222, 1222, 1227, 1232, 1238, 1245, 1246, 1251, 1255, 1260, 1264, 1271, 1275, 1279, 1284, 1291, 1292, 1295, 1300, 1304, 1309, 1314, 1319, 1324, 1330, 1334, 1337, 1341, 1345, 1350, 1355, 1360, 1365, 1370, 1375, 1380, 1385, 1389, 1392, 1392, 1404, 1405, 1405, 1410, 1410, 1417, 1423, 1427, 1430, 1434, 1440, 1444, 1448, 1454, 1455, 1456, 1457, 1458, 1459, 1460, 1461, 1462, 1467, 1466, 1479, 1479, 1484, 1489, 1493, 1497, 1505, 1514, 1518, 1522, 1526, 1530, 1534, 1534, 1539, 1545, 1546, 1555, 1568, 1581, 1581, 1581, 1591, 1591, 1591, 1601, 1608, 1612, 1616, 1616, 1616, 1624, 1623, 1640, 1645, 1639, 1662, 1661, 1678, 1677, 1695, 1696, 1695, 1710, 1714, 1718, 1722, 1728, 1735, 1736, 1737, 1738, 1741, 1742, 1743, 1746, 1747, 1756, 1757, 1763, 1764, 1767, 1768, 1772, 1776, 1783, 1787, 1782, 1797, 1806, 1810, 1816, 1821, 1826, 1831, 1835, 1839, 1846, 1850, 1845, 1858, 1862, 1857, 1871, 1878, 1879, 1883, 1889, 1890, 1893, 1904, 1907, 1911, 1912, 1915, 1919, 1922, 1930, 1933, 1934, 1938, 1941, 1954, 1955, 1961, 1967, 1990, 2023, 2027, 2034, 2037, 2043, 2044, 2050, 2054, 2061, 2064, 2071, 2074, 2081, 2084, 2090, 2092, 2091, 2103, 2102, 2123, 2124, 2125, 2126, 2129, 2136, 2137, 2138, 2139, 2142, 2176, 2177, 2178, 2182, 2188, 2189, 2190, 2191, 2192, 2193, 2194, 2195, 2196, 2197, 2198, 2201, 2207, 2213, 2214, 2217, 2222, 2221, 2229, 2232, 2238, 2244, 2248, 2252, 2256, 2260, 2264, 2268, 2272, 2277, 2282, 2286, 2290, 2294, 2298, 2309, 2310, 2316, 2326, 2331, 2337, 2338, 2341, 2352, 2363, 2364, 2367, 2377, 2381, 2384, 2389, 2389, 2414, 2415, 2419, 2428, 2429, 2435, 2441, 2442, 2443, 2446, 2447, 2448, 2449, 2452, 2453, 2454, 2457, 2458, 2461, 2462, 2465, 2466, 2469, 2470, 2471, 2474, 2475, 2478, 2479, 2482 }; #endif #if YYDEBUG || YYERROR_VERBOSE || YYTOKEN_TABLE /* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. First, the terminals, then, starting at YYNTOKENS, nonterminals. */ static const char *const yytname[] = { "$end", "error", "$undefined", "kCLASS", "kMODULE", "kDEF", "kUNDEF", "kBEGIN", "kRESCUE", "kENSURE", "kEND", "kIF", "kUNLESS", "kTHEN", "kELSIF", "kELSE", "kCASE", "kWHEN", "kWHILE", "kUNTIL", "kFOR", "kBREAK", "kNEXT", "kREDO", "kRETRY", "kIN", "kDO", "kDO_COND", "kDO_BLOCK", "kRETURN", "kYIELD", "kSUPER", "kSELF", "kNIL", "kTRUE", "kFALSE", "kAND", "kOR", "kNOT", "kIF_MOD", "kUNLESS_MOD", "kWHILE_MOD", "kUNTIL_MOD", "kRESCUE_MOD", "kALIAS", "kDEFINED", "klBEGIN", "klEND", "k__LINE__", "k__FILE__", "tIDENTIFIER", "tFID", "tGVAR", "tIVAR", "tCONSTANT", "tCVAR", "tINTEGER", "tFLOAT", "tSTRING_CONTENT", "tNTH_REF", "tBACK_REF", "tREGEXP_END", "tUPLUS", "tUMINUS", "tPOW", "tCMP", "tEQ", "tEQQ", "tNEQ", "tGEQ", "tLEQ", "tANDOP", "tOROP", "tMATCH", "tNMATCH", "tDOT2", "tDOT3", "tAREF", "tASET", "tLSHFT", "tRSHFT", "tCOLON2", "tCOLON3", "tOP_ASGN", "tASSOC", "tLPAREN", "tLPAREN_ARG", "tRPAREN", "tLBRACK", "tLBRACE", "tLBRACE_ARG", "tSTAR", "tAMPER", "tSYMBEG", "tSTRING_BEG", "tXSTRING_BEG", "tREGEXP_BEG", "tWORDS_BEG", "tQWORDS_BEG", "tSTRING_DBEG", "tSTRING_DVAR", "tSTRING_END", "tLOWEST", "'='", "'?'", "':'", "'>'", "'<'", "'|'", "'^'", "'&'", "'+'", "'-'", "'*'", "'/'", "'%'", "tUMINUS_NUM", "'!'", "'~'", "tLAST_TOKEN", "'{'", "'}'", "'['", "']'", "'.'", "')'", "','", "'`'", "'('", "' '", "'\\n'", "';'", "$accept", "program", "@1", "bodystmt", "compstmt", "stmts", "stmt", "@2", "@3", "expr", "expr_value", "command_call", "block_command", "cmd_brace_block", "@4", "@5", "command", "mlhs", "mlhs_entry", "mlhs_basic", "mlhs_item", "mlhs_head", "mlhs_node", "lhs", "cname", "cpath", "fname", "fsym", "fitem", "undef_list", "@6", "op", "reswords", "arg", "@7", "arg_value", "aref_args", "paren_args", "opt_paren_args", "call_args", "call_args2", "command_args", "@8", "open_args", "@9", "@10", "block_arg", "opt_block_arg", "args", "mrhs", "primary", "@11", "@12", "@13", "@14", "@15", "@16", "@17", "@18", "@19", "@20", "@21", "@22", "@23", "@24", "@25", "@26", "primary_value", "then", "do", "if_tail", "opt_else", "block_var", "opt_block_var", "do_block", "@27", "@28", "block_call", "method_call", "brace_block", "@29", "@30", "@31", "@32", "case_body", "when_args", "cases", "opt_rescue", "exc_list", "exc_var", "opt_ensure", "literal", "strings", "string", "string1", "xstring", "regexp", "words", "word_list", "word", "qwords", "qword_list", "string_contents", "xstring_contents", "string_content", "@33", "@34", "string_dvar", "symbol", "sym", "dsym", "numeric", "variable", "var_ref", "var_lhs", "backref", "superclass", "@35", "f_arglist", "f_args", "f_norm_arg", "f_arg", "f_opt", "f_optarg", "restarg_mark", "f_rest_arg", "blkarg_mark", "f_block_arg", "opt_f_block_arg", "singleton", "@36", "assoc_list", "assocs", "assoc", "operation", "operation2", "operation3", "dot_or_colon", "opt_terms", "opt_nl", "trailer", "term", "terms", "none", 0 }; #endif # ifdef YYPRINT /* YYTOKNUM[YYLEX-NUM] -- Internal token number corresponding to token YYLEX-NUM. */ static const yytype_uint16 yytoknum[] = { 0, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 61, 63, 58, 62, 60, 124, 94, 38, 43, 45, 42, 47, 37, 358, 33, 126, 359, 123, 125, 91, 93, 46, 41, 44, 96, 40, 32, 10, 59 }; # endif /* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ static const yytype_uint16 yyr1[] = { 0, 132, 134, 133, 135, 136, 137, 137, 137, 137, 139, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 140, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 138, 141, 141, 141, 141, 141, 141, 142, 143, 143, 143, 143, 143, 144, 144, 144, 146, 147, 145, 148, 148, 148, 148, 148, 148, 148, 148, 149, 149, 150, 150, 151, 151, 151, 151, 151, 151, 152, 152, 153, 153, 154, 154, 154, 154, 154, 154, 154, 154, 155, 155, 155, 155, 155, 155, 155, 155, 156, 156, 157, 157, 157, 158, 158, 158, 158, 158, 159, 159, 160, 160, 161, 162, 161, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 166, 165, 165, 165, 167, 168, 168, 168, 168, 168, 168, 169, 169, 169, 169, 170, 170, 171, 171, 171, 171, 171, 171, 171, 171, 171, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 172, 174, 173, 175, 176, 175, 177, 175, 178, 179, 179, 180, 180, 181, 181, 181, 182, 182, 182, 182, 182, 182, 182, 182, 182, 183, 182, 184, 182, 182, 182, 182, 182, 182, 182, 182, 182, 182, 182, 185, 182, 182, 182, 182, 182, 182, 186, 187, 182, 188, 189, 182, 182, 182, 182, 190, 191, 182, 192, 182, 193, 194, 182, 195, 182, 196, 182, 197, 198, 182, 182, 182, 182, 182, 199, 200, 200, 200, 200, 201, 201, 201, 202, 202, 203, 203, 204, 204, 205, 205, 205, 205, 207, 208, 206, 209, 209, 209, 210, 210, 210, 210, 210, 210, 212, 213, 211, 214, 215, 211, 216, 217, 217, 217, 218, 218, 219, 219, 220, 220, 220, 221, 221, 222, 222, 223, 223, 223, 224, 225, 225, 226, 227, 228, 229, 229, 230, 230, 231, 231, 232, 232, 233, 233, 234, 234, 235, 235, 236, 237, 236, 238, 236, 239, 239, 239, 239, 240, 241, 241, 241, 241, 242, 243, 243, 243, 243, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 245, 246, 247, 247, 248, 249, 248, 248, 250, 250, 251, 251, 251, 251, 251, 251, 251, 251, 251, 252, 252, 252, 252, 252, 253, 253, 254, 255, 255, 256, 256, 257, 257, 258, 258, 259, 260, 260, 261, 262, 261, 263, 263, 263, 264, 264, 265, 266, 266, 266, 267, 267, 267, 267, 268, 268, 268, 269, 269, 270, 270, 271, 271, 272, 272, 272, 273, 273, 274, 274, 275 }; /* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN. */ static const yytype_uint8 yyr2[] = { 0, 2, 0, 2, 4, 2, 1, 1, 3, 2, 0, 4, 3, 3, 3, 2, 3, 3, 3, 3, 3, 0, 5, 4, 3, 3, 3, 6, 5, 5, 5, 3, 3, 3, 3, 1, 1, 3, 3, 2, 2, 1, 1, 1, 1, 2, 2, 2, 1, 4, 4, 0, 0, 6, 2, 3, 4, 5, 4, 5, 2, 2, 1, 3, 1, 3, 1, 2, 3, 2, 2, 1, 1, 3, 2, 3, 1, 4, 3, 3, 3, 3, 2, 1, 1, 4, 3, 3, 3, 3, 2, 1, 1, 1, 2, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 5, 3, 6, 5, 5, 5, 5, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 3, 3, 3, 3, 0, 4, 5, 1, 1, 1, 2, 2, 5, 2, 3, 3, 4, 4, 6, 1, 1, 1, 2, 5, 2, 5, 4, 7, 3, 1, 4, 3, 5, 7, 2, 5, 4, 6, 7, 9, 3, 1, 0, 2, 1, 0, 3, 0, 4, 2, 2, 1, 1, 3, 3, 4, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 4, 0, 5, 3, 3, 2, 4, 3, 3, 1, 4, 3, 1, 0, 6, 2, 1, 2, 6, 6, 0, 0, 7, 0, 0, 7, 5, 4, 5, 0, 0, 9, 0, 6, 0, 0, 8, 0, 5, 0, 6, 0, 0, 9, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 5, 1, 2, 1, 1, 1, 2, 1, 3, 0, 0, 6, 2, 4, 4, 2, 4, 4, 3, 2, 1, 0, 0, 6, 0, 0, 6, 5, 1, 4, 2, 1, 1, 6, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 2, 3, 3, 3, 3, 3, 0, 3, 1, 2, 3, 3, 0, 3, 0, 2, 0, 2, 1, 0, 3, 0, 4, 1, 1, 1, 1, 2, 1, 1, 1, 1, 3, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 4, 2, 4, 2, 6, 4, 4, 2, 4, 2, 2, 1, 0, 1, 1, 1, 1, 1, 1, 3, 3, 1, 3, 1, 1, 2, 1, 1, 1, 2, 2, 1, 1, 0, 5, 1, 2, 2, 1, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 2, 0 }; /* YYDEFACT[STATE-NAME] -- Default rule to reduce with in state STATE-NUM when YYTABLE doesn't specify something else to do. Zero means the default is an error. */ static const yytype_uint16 yydefact[] = { 2, 0, 0, 1, 0, 0, 0, 0, 0, 278, 0, 0, 490, 299, 302, 0, 323, 324, 325, 326, 288, 291, 356, 426, 425, 427, 428, 0, 0, 492, 21, 0, 430, 429, 420, 277, 422, 421, 423, 424, 416, 417, 433, 434, 0, 0, 0, 0, 0, 501, 501, 71, 399, 397, 399, 399, 389, 395, 0, 0, 0, 3, 490, 7, 35, 36, 44, 43, 0, 62, 0, 66, 72, 0, 41, 219, 0, 48, 295, 269, 270, 381, 382, 271, 272, 273, 274, 379, 380, 378, 431, 275, 0, 276, 254, 6, 9, 323, 324, 288, 291, 356, 492, 92, 93, 0, 0, 0, 0, 95, 0, 327, 0, 431, 276, 0, 316, 144, 155, 145, 168, 141, 161, 151, 150, 171, 172, 166, 149, 148, 143, 169, 173, 174, 153, 142, 156, 160, 162, 154, 147, 163, 170, 165, 164, 157, 167, 152, 140, 159, 158, 139, 146, 137, 138, 135, 136, 97, 99, 98, 130, 131, 128, 112, 113, 114, 117, 119, 115, 132, 133, 120, 121, 125, 116, 118, 109, 110, 111, 122, 123, 124, 126, 127, 129, 134, 470, 318, 100, 101, 469, 0, 164, 157, 167, 152, 135, 136, 97, 98, 102, 104, 106, 15, 103, 105, 0, 0, 42, 0, 0, 0, 431, 0, 276, 0, 498, 497, 490, 0, 499, 491, 0, 0, 0, 340, 339, 0, 0, 431, 276, 0, 0, 0, 233, 220, 264, 46, 241, 501, 501, 475, 47, 45, 0, 61, 0, 501, 355, 60, 39, 0, 10, 493, 216, 0, 0, 195, 0, 196, 284, 0, 0, 0, 62, 280, 0, 492, 0, 494, 494, 221, 494, 0, 494, 472, 0, 70, 0, 76, 83, 413, 412, 414, 411, 0, 410, 0, 0, 0, 0, 0, 0, 0, 418, 419, 40, 210, 211, 5, 491, 0, 0, 0, 0, 0, 0, 0, 345, 348, 0, 74, 0, 69, 67, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 501, 0, 0, 0, 360, 357, 296, 383, 0, 0, 351, 54, 294, 0, 313, 92, 93, 94, 418, 419, 0, 436, 311, 435, 0, 501, 0, 0, 0, 449, 489, 488, 320, 107, 0, 501, 284, 330, 329, 0, 328, 0, 0, 501, 0, 0, 0, 0, 0, 0, 0, 0, 500, 0, 0, 284, 0, 501, 0, 308, 501, 261, 0, 0, 234, 263, 0, 236, 290, 0, 257, 256, 255, 233, 492, 501, 492, 0, 12, 14, 13, 0, 292, 0, 0, 0, 0, 0, 0, 0, 282, 63, 492, 492, 222, 286, 496, 495, 223, 496, 225, 496, 474, 287, 473, 82, 0, 501, 0, 401, 404, 402, 415, 400, 384, 398, 385, 386, 387, 388, 0, 391, 393, 0, 394, 0, 0, 8, 16, 17, 18, 19, 20, 37, 38, 501, 0, 25, 33, 0, 34, 0, 68, 75, 24, 175, 264, 32, 192, 200, 205, 206, 207, 202, 204, 214, 215, 208, 209, 185, 186, 212, 213, 0, 201, 203, 197, 198, 199, 187, 188, 189, 190, 191, 481, 486, 482, 487, 254, 354, 0, 481, 483, 482, 484, 501, 481, 482, 254, 254, 501, 501, 26, 177, 31, 184, 51, 55, 0, 438, 0, 0, 92, 93, 96, 0, 0, 501, 0, 492, 454, 452, 451, 450, 453, 461, 465, 464, 460, 449, 0, 0, 455, 501, 458, 501, 463, 501, 0, 448, 0, 0, 279, 501, 501, 370, 501, 331, 175, 485, 283, 0, 481, 482, 501, 0, 0, 0, 364, 0, 306, 334, 333, 300, 332, 303, 485, 283, 0, 481, 482, 0, 0, 240, 477, 0, 265, 262, 501, 0, 0, 476, 289, 0, 41, 0, 259, 0, 253, 501, 0, 0, 0, 0, 0, 227, 11, 0, 217, 0, 23, 183, 63, 0, 226, 0, 265, 79, 81, 0, 481, 482, 0, 0, 390, 392, 396, 193, 194, 343, 0, 346, 341, 268, 0, 73, 0, 0, 0, 0, 353, 58, 285, 0, 0, 232, 352, 56, 231, 350, 50, 349, 49, 361, 358, 501, 314, 0, 0, 285, 317, 0, 0, 492, 0, 440, 0, 444, 468, 0, 446, 462, 0, 447, 466, 321, 108, 371, 372, 501, 373, 0, 501, 337, 0, 0, 335, 0, 285, 0, 0, 0, 305, 307, 366, 0, 0, 0, 0, 285, 0, 501, 0, 238, 501, 501, 0, 0, 258, 0, 246, 228, 0, 492, 501, 501, 229, 0, 22, 281, 492, 77, 0, 406, 407, 408, 403, 409, 342, 0, 0, 0, 266, 176, 218, 30, 181, 182, 59, 0, 28, 179, 29, 180, 57, 0, 0, 52, 0, 437, 312, 471, 457, 0, 319, 456, 501, 501, 467, 0, 459, 501, 449, 0, 0, 375, 338, 0, 4, 377, 0, 297, 0, 298, 0, 501, 0, 0, 309, 235, 0, 237, 252, 0, 243, 501, 501, 260, 0, 0, 293, 224, 405, 344, 0, 267, 27, 178, 0, 0, 0, 0, 439, 0, 442, 443, 445, 0, 0, 374, 0, 84, 91, 0, 376, 0, 365, 367, 368, 363, 301, 304, 0, 501, 501, 0, 242, 0, 248, 501, 230, 347, 362, 359, 0, 315, 501, 0, 90, 0, 501, 0, 501, 501, 0, 239, 244, 0, 501, 0, 247, 53, 441, 322, 485, 89, 0, 481, 482, 369, 336, 310, 501, 0, 249, 501, 85, 245, 0, 250, 501, 251 }; /* YYDEFGOTO[NTERM-NUM]. */ static const yytype_int16 yydefgoto[] = { -1, 1, 2, 377, 378, 62, 63, 424, 255, 64, 209, 65, 66, 540, 678, 823, 67, 68, 263, 69, 70, 71, 72, 210, 109, 110, 200, 201, 202, 203, 574, 527, 189, 74, 426, 236, 268, 668, 669, 237, 619, 245, 246, 415, 620, 730, 610, 407, 269, 483, 75, 206, 435, 630, 222, 720, 223, 721, 604, 845, 544, 541, 771, 370, 372, 573, 785, 258, 382, 596, 708, 709, 228, 654, 309, 478, 753, 77, 78, 355, 534, 769, 533, 768, 394, 592, 842, 577, 702, 787, 791, 79, 80, 81, 82, 83, 84, 85, 291, 463, 86, 293, 287, 285, 456, 646, 645, 749, 87, 286, 88, 89, 212, 91, 213, 214, 365, 543, 563, 564, 565, 566, 567, 568, 569, 570, 571, 781, 690, 191, 371, 273, 270, 241, 115, 548, 522, 375, 219, 254, 441, 383, 221, 95 }; /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing STATE-NUM. */ #define YYPACT_NINF -654 static const yytype_int16 yypact[] = { -654, 73, 2254, -654, 5471, 7939, 8236, 3906, 5121, -654, 6810, 6810, 3777, -654, -654, 8038, 5677, 5677, -654, -654, 5677, 4436, 4539, -654, -654, -654, -654, 6810, 4996, -44, -654, -3, -654, -654, 2117, 4024, -654, -654, 4127, -654, -654, -654, -654, -654, 7634, 7634, 50, 3187, 6810, 6913, 7634, 8335, 4871, -654, -654, -654, -18, -1, 94, 7737, 7634, -654, 124, 757, 249, -654, -654, 104, 74, -654, 57, 8137, -654, 96, 9349, 171, 485, 11, 32, -654, -654, 120, -654, -654, -654, -654, -654, -654, -654, -654, 10, -654, 64, 38, 42, -654, 757, -654, -654, -654, 99, 111, -44, 129, 140, 6810, 90, 3318, 325, -654, 78, -654, 549, -654, -654, 42, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, 95, 148, 282, 333, -654, -654, -654, -654, -654, -654, -654, 336, 337, 465, -654, 473, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, 495, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, 147, -654, -654, 2615, 225, 249, 48, 188, 558, 31, 213, 258, 48, -654, -654, 124, 88, -654, 209, 6810, 6810, 288, -654, -654, 559, 324, 66, 245, 7634, 7634, 7634, -654, 9349, 274, -654, -654, 262, 269, -654, -654, -654, 5363, -654, 5780, 5677, -654, -654, -654, 222, -654, -654, 275, 285, 3449, -654, 571, 366, 476, 3187, 310, 315, 342, 249, 7634, -44, 371, 392, 401, -654, 480, 375, 401, -654, 470, -654, 586, 595, 618, -654, -654, -654, -654, 231, -654, 482, 515, 674, 425, 565, 444, 30, 493, 499, -654, -654, -654, -654, 3674, 6810, 6810, 6810, 6810, 5471, 6810, 6810, -654, -654, 7016, -654, 3187, 8335, 438, 7016, 7634, 7634, 7634, 7634, 7634, 7634, 7634, 7634, 7634, 7634, 7634, 7634, 7634, 7634, 7634, 7634, 7634, 7634, 7634, 7634, 7634, 7634, 7634, 7634, 7634, 7634, 1612, 6913, 8489, 8555, 8555, -654, -654, -654, -654, 7737, 7737, -654, 501, -654, 275, 249, -654, 623, -654, -654, -654, 124, -654, -654, -654, 8621, 6913, 8555, 2615, 6810, 419, -654, -654, -654, -654, 583, 596, 283, -654, -654, 2737, 590, 7634, 8687, 6913, 8753, 7634, 7634, 2981, 601, 3571, 7119, 611, -654, 18, 18, 252, 8819, 6913, 8885, -654, 498, -654, 7634, 5883, -654, -654, 5986, -654, -654, 503, 5574, -654, -654, 104, -44, 510, 9, 512, -654, -654, -654, 5121, -654, 7634, 3449, 528, 8687, 8753, 7634, 529, -654, 527, -44, 9213, -654, -654, 7222, -654, -654, 7634, -654, 7634, -654, -654, -654, 623, 8951, 6913, 9017, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, 25, -654, -654, 534, -654, 7634, 7634, 757, -654, -654, -654, -654, -654, -654, -654, 37, 7634, -654, 544, 546, -654, 550, -654, -654, -654, 1775, -654, -654, 366, 9418, 9418, 9418, 9418, 547, 547, 9435, 1667, 9418, 9418, 9366, 9366, 394, 394, 2464, 547, 547, 714, 714, 437, 43, 43, 366, 366, 366, 2365, 4642, 2484, 4745, 111, -654, 554, 487, -654, 585, -654, 4539, -654, -654, 1899, 1899, 37, 37, -654, 9349, -654, 9349, -654, -654, 124, -654, 6810, 2615, 141, 477, -654, 111, 562, 111, 681, 35, 597, -654, -654, -654, -654, -654, -654, -654, -654, 429, 2615, 124, -654, 575, -654, 577, 655, 588, 665, -654, 5246, 5121, -654, 7325, 703, -654, 398, -654, 2345, 4230, 4333, 599, 344, 416, 703, 726, 734, 7634, 626, 48, -654, -654, -654, -654, -654, -654, 60, 85, 635, 259, 354, 6810, 657, -654, -654, 7634, 274, -654, 628, 7634, 274, -654, -654, 7634, 9265, 16, -654, 636, -654, 634, 643, 6089, 8555, 8555, 645, -654, -654, 6810, 9349, 656, -654, 9349, 296, 658, -654, 7634, -654, 141, 477, 661, 340, 351, 3449, 671, -654, -654, -654, 366, 366, -654, 7840, -654, -654, -654, 7428, -654, 7634, 7634, 7737, 7634, -654, 501, 624, 7737, 7737, -654, -654, 501, -654, -654, -654, -654, -654, -654, -654, 37, -654, 124, 776, -654, -654, 663, 7634, -44, 781, -654, 429, -654, -654, 452, -654, -654, -12, -654, -654, -654, -654, 544, -654, 708, -654, 3084, 792, -654, 6810, 797, -654, 7634, 432, 7634, 7634, 798, -654, -654, -654, 7531, 2859, 3571, 3571, 367, 18, 498, 6192, -654, 498, 498, 6295, 685, -654, 6398, -654, -654, 104, 9, 111, 111, -654, 101, -654, -654, 9213, 631, 690, -654, -654, -654, -654, -654, -654, 709, 3571, 7634, 692, 9349, 9349, -654, 9349, 9349, -654, 7737, -654, 9349, -654, 9349, -654, 3571, 3449, -654, 2615, -654, -654, -654, -654, 691, -654, -654, 693, 588, -654, 597, -654, 588, 419, 8434, 48, -654, -654, 3571, -654, -654, 48, -654, 7634, -654, 7634, 165, 810, 812, -654, -654, 7634, -654, -654, 7634, -654, 705, 710, -654, 7634, 712, -654, -654, -654, -654, 825, -654, -654, 9349, 830, 720, 3449, 832, -654, 452, -654, -654, -654, 2615, 789, -654, 640, 595, 618, 2615, -654, 2737, -654, -654, -654, -654, -654, -654, 3571, 9286, 498, 6501, -654, 6604, -654, 498, -654, -654, -654, -654, 724, -654, 588, 838, 623, 9083, 6913, 9149, 596, 398, 839, -654, -654, 7634, 727, 7634, -654, -654, -654, -654, 41, 477, 732, 76, 93, -654, -654, -654, 498, 6707, -654, 498, 631, -654, 7634, -654, 498, -654 }; /* YYPGOTO[NTERM-NUM]. */ static const yytype_int16 yypgoto[] = { -654, -654, -654, -360, 431, -654, 59, -654, -654, 958, 44, 23, -654, -569, -654, -654, 1, -6, -183, 22, 779, -654, -25, 927, -70, 853, 7, -654, 21, -654, -654, -5, -654, -16, -654, 1378, -338, -7, -479, 82, -654, 2, -654, -654, -654, -654, -9, 158, 110, -278, 26, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, -654, 185, -209, -372, 0, -520, 208, -458, -654, -654, -654, -228, -654, 785, -654, -654, -654, -654, -356, -654, -654, 3, -654, -654, -654, -654, -654, -654, 784, -654, -654, -654, -654, -654, -654, -654, -654, 458, -217, -654, -654, -654, 12, -654, 14, -654, 627, 860, 1246, 1107, -654, -654, 84, 309, 183, -654, -653, 184, -654, -608, -654, -321, -504, -654, -654, -654, -4, -382, 755, -226, -654, -654, -24, 20, 467, 53, 814, 756 }; /* YYTABLE[YYPACT[STATE-NUM]]. What to do in state STATE-NUM. If positive, shift that token. If negative, reduce the rule which number is the opposite. If zero, do what YYDEFACT says. If YYTABLE_NINF, syntax error. */ #define YYTABLE_NINF -502 static const yytype_int16 yytable[] = { 235, 235, 188, 188, 235, 523, 390, 238, 238, 225, 551, 238, 240, 240, 187, 248, 240, 234, 234, 419, 204, 234, 205, 188, 249, 598, 277, 614, 257, 259, 549, 111, 111, 235, 235, 588, 360, 490, 299, 783, 204, 111, 205, 297, 298, 594, 274, 188, 584, 252, 267, 572, 672, 674, -87, 215, 218, 705, 347, 284, 614, 380, 601, 96, 693, 220, 696, 714, 347, 264, 458, 306, 307, 3, 464, 676, 677, 111, 432, 363, 559, 780, 296, 452, 784, -87, 253, 353, 466, -86, 625, -84, 345, -432, 248, 761, 354, 111, 560, 242, 405, 767, 243, 392, 260, 393, -88, 316, 353, 652, -89, 290, 642, -84, -432, 220, 521, 256, 528, 531, 532, 352, 356, 595, 453, 454, 239, 239, 292, 484, 239, 467, 308, 626, -84, 346, -76, 306, 307, 253, 358, -91, 729, 550, 359, 653, -87, 351, 216, 217, 294, 295, 348, 381, 647, -478, 339, 340, 341, 521, 272, 528, 348, 366, -83, 253, -479, -485, -87, -481, 247, -87, -87, 783, -84, 550, -426, 310, 216, 217, 704, -86, 393, 311, 681, 364, -79, 76, -481, 76, 112, 112, -76, -89, 391, 211, 211, 211, -88, 315, 227, 211, 211, 687, 550, 211, -86, -86, 216, 217, -420, -81, 211, -482, 53, 235, 235, 297, 859, -426, 770, -423, -485, -88, -88, 550, 813, 244, 235, -425, 235, 235, 76, 211, 211, 238, 278, 238, 238, 247, 240, 572, 240, 240, 211, 234, 648, 234, 416, -478, 436, -420, -327, -420, 216, 217, 278, -478, 672, 674, -479, -485, -423, -485, -423, -485, 396, 397, -479, -481, -91, 220, -425, 376, 421, 827, 828, -90, 840, 379, 829, 422, 423, 264, -86, 306, 307, 437, 485, 452, 211, 384, 76, -327, 235, -327, 388, 547, 701, 488, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 412, 235, 414, 417, 453, 454, 455, 480, 264, 536, 538, 520, 487, 111, 395, 389, 398, 614, 267, 471, 472, 473, 474, 402, 614, 801, 235, -91, 239, 875, 239, 418, 405, 470, -90, -91, 520, -427, 475, -78, 431, -86, 581, 267, 235, -83, 536, 538, 535, 537, -80, 235, -82, -88, 520, 361, 362, 719, 235, -78, -90, 267, 406, 235, 235, 76, -85, 235, 520, 409, 736, 617, 410, 737, 738, 267, 611, 425, 621, 427, -427, 211, 211, 622, 631, 824, 707, 704, -428, 634, 542, -430, -429, 188, 482, -65, -73, 235, 520, 482, 235, 712, 235, 211, 316, 211, 211, 61, 235, 433, 204, 623, 205, 627, 434, 76, 841, -78, 520, 629, 76, -86, -78, 597, 597, 267, 650, 651, -80, 636, 637, -428, 316, -80, -430, -429, -88, 235, 572, -78, -78, -64, 614, 553, 860, 554, 555, 556, 557, -85, -80, -80, 262, 553, -80, 554, 555, 556, 557, 76, 211, 211, 211, 211, 76, 211, 211, -77, 438, 211, 446, 76, 278, 713, 211, 316, 782, 591, 614, 337, 338, 339, 340, 341, 558, 559, 288, 289, 663, 795, 329, 330, 439, -88, 558, 559, 440, 664, 448, 879, 461, 442, 211, 560, 670, 440, 561, 673, 675, -85, 211, 211, 262, 560, 452, 663, 561, 558, 559, 465, -420, 562, 337, 338, 339, 340, 341, 211, -423, 76, 211, 468, -283, 431, 235, 606, 560, 469, 486, 561, 342, 76, 188, 188, 666, 211, 684, 452, 235, 76, 373, 76, 836, -90, 698, 453, 454, 457, 838, 211, 204, 680, 205, -420, -86, 539, 235, 575, 679, 699, 235, -423, 211, -283, 235, -283, -82, 580, 576, -482, 444, 343, 235, 344, 440, 316, 76, -78, 453, 454, 459, 688, 393, 374, 611, 593, 743, 452, 605, 735, 329, 330, 615, 90, 367, 90, 113, 113, 113, 211, 624, 628, 550, 385, 399, 235, 229, 756, 757, 759, 760, 225, 723, 633, 764, 766, 429, -73, 635, 334, 335, 336, 337, 338, 339, 340, 341, 649, 453, 454, 462, 449, 667, 235, -264, 368, 657, 369, 90, 658, -431, 665, 279, 111, 386, 400, 387, 401, 758, 682, 482, 428, -88, 763, 765, 683, 262, 386, 759, 430, 764, 766, 279, -276, 685, 689, 235, 692, -284, 694, 776, 762, 450, 235, 451, -80, -285, 235, 695, 697, 235, -431, 704, -431, 807, 862, 711, 746, 747, 809, 748, -85, 211, 76, 42, 43, 452, 772, 90, 460, 715, 443, 235, 445, -276, 447, -276, 262, 716, -284, 820, -284, 76, 232, -77, 793, 718, -285, 725, -285, 812, 94, 722, 94, 732, 731, 863, 814, 864, 94, 94, 94, 734, 726, 739, 94, 94, 453, 454, 94, 597, 741, 316, 820, 733, 235, 94, 742, 744, 819, 773, 846, 774, 211, 235, 777, 786, 329, 330, 235, 301, 302, 303, 304, 305, 790, 94, 94, 94, 271, 275, 794, 796, 211, 810, 815, 111, 579, 94, 211, 825, 816, -265, 826, 843, 587, 844, 589, 336, 337, 338, 339, 340, 341, 76, 848, 235, 90, 235, 854, 850, 853, 227, 808, 855, 856, 858, 861, 871, 874, 211, 235, 876, 884, 314, 211, 211, 886, 235, 889, 235, 520, 632, 116, 94, 752, 94, 349, 267, 350, 883, 190, 882, 830, 235, 686, 778, 779, 0, 235, 300, 0, 0, 0, 0, 0, 802, 90, 0, 804, 805, 0, 90, 76, 0, 0, 211, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 76, 76, 76, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 90, 0, 73, 0, 73, 90, 0, 0, 0, 0, 0, 76, 90, 279, 0, 226, 0, 0, 0, 0, 211, 0, 0, 0, 0, 0, 76, 76, 0, 76, 0, 0, 0, 0, 94, 0, 0, 0, 0, 849, 851, 208, 208, 208, 833, 0, 0, 73, 76, 0, 94, 94, 0, 0, 0, 0, 0, 0, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 408, 408, 90, 0, 94, 0, 94, 94, 420, 868, 869, 265, 0, 76, 90, 873, 94, 0, 0, 0, 76, 94, 90, 0, 90, 0, 76, 0, 76, 0, 0, 0, 0, 0, 887, 76, 0, 0, 0, 73, 0, 0, 0, 0, 0, 0, 0, 0, 890, 0, 0, 892, 0, 211, 0, 0, 894, 0, 0, 90, 94, 94, 94, 94, 94, 94, 94, 94, 357, 0, 94, 0, 94, 0, 0, 94, 0, 0, 0, 0, 0, 745, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 94, 271, 0, 0, 0, 0, 0, 0, 94, 94, 0, 93, 0, 93, 114, 114, 0, 0, 0, 0, 0, 0, 0, 0, 230, 94, 271, 94, 94, 0, 0, 0, 0, 0, 0, 73, 578, 789, 0, 94, 0, 0, 0, 94, 271, 0, 0, 94, 0, 94, 0, 0, 798, 799, 800, 0, 93, 94, 271, 0, 280, 408, 0, 0, 0, 0, 0, 0, 0, 0, 94, 0, 0, 90, 0, 0, 408, 0, 0, 0, 280, 0, 208, 208, 94, 73, 817, 0, 0, 0, 73, 0, 90, 0, 0, 0, 0, 0, 0, 0, 0, 821, 822, 0, 0, 0, 0, 94, 271, 0, 0, 0, 0, 0, 0, 0, 93, 0, 0, 0, 0, 0, 0, 837, 0, 0, 0, 0, 0, 73, 0, 0, 0, 0, 73, 0, 655, 0, 0, 0, 0, 73, 0, 0, 0, 0, 0, 0, 0, 0, 92, 0, 92, 0, 0, 0, 857, 0, 0, 0, 0, 208, 208, 208, 208, 0, 476, 477, 0, 865, 0, 866, 0, 0, 90, 0, 0, 0, 867, 0, 0, 0, 229, 0, 0, 0, 671, 0, 0, 671, 671, 655, 655, 0, 0, 92, 0, 0, 0, 73, 94, 94, 0, 0, 0, 0, 0, 0, 671, 0, 0, 73, 0, 0, 0, 93, 0, 0, 0, 73, 94, 73, 0, 0, 691, 0, 691, 0, 691, 0, 0, 552, 0, 90, 703, 706, 0, 706, 0, 0, 0, 0, 0, 0, 0, 706, 0, 0, 90, 90, 90, 0, 0, 0, 0, 92, 73, 0, 0, 0, 0, 94, 0, 0, 0, 93, 0, 0, 0, 408, 93, 0, 0, 265, 0, 0, 0, 0, 0, 0, 408, 94, 90, 0, 0, 0, 0, 94, 0, 0, 0, 0, 0, 0, 0, 0, 0, 90, 90, 0, 90, 0, 94, 0, 0, 0, 0, 0, 0, 93, 0, 0, 0, 0, 93, 834, 0, 0, 94, 90, 0, 93, 280, 94, 94, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 655, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 90, 0, 92, 0, 0, 0, 0, 90, 788, 94, 0, 792, 94, 90, 0, 90, 0, 0, 0, 0, 0, 73, 90, 0, 94, 94, 94, 93, 0, 0, 408, 0, 0, 408, 408, 0, 0, 0, 0, 93, 73, 0, 0, 671, 671, 0, 0, 93, 0, 93, 0, 208, 92, 0, 0, 0, 0, 92, 94, 0, 0, 0, 0, 0, 0, 0, 0, 94, 0, 0, 0, 0, 0, 94, 94, 0, 94, 0, 0, 0, 0, 0, 0, 0, 93, 691, 691, 0, 0, 0, 691, 0, 0, 0, 0, 94, 92, 0, 0, 0, 0, 92, 0, 0, 706, 0, 0, 0, 92, 0, 0, 0, 208, 0, 408, 408, 0, 0, 0, 0, 0, 0, 73, 0, 0, 0, 0, 0, 94, 0, 226, 0, 0, 0, 0, 94, 0, 0, 740, 0, 0, 94, 0, 94, 0, 0, 0, 0, 0, 0, 94, 0, 408, 408, 0, 0, 0, 0, 408, 403, 404, 0, 0, 0, 0, 691, 92, 0, 94, 271, 0, 578, 706, 0, 0, 0, 0, 408, 92, 0, 0, 73, 0, 0, 0, 0, 92, 0, 92, 0, 0, 408, 0, 0, 408, 0, 73, 73, 73, 408, 0, 93, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 517, 518, 0, 208, 519, 0, 0, 0, 93, 0, 0, 92, 160, 161, 162, 163, 164, 165, 73, 166, 167, 0, 0, 168, 0, 0, 481, 169, 170, 171, 172, 489, 0, 73, 73, 0, 73, 0, 0, 0, 0, 173, 0, 0, 0, 0, 0, 0, 0, 0, 0, 832, 0, 0, 0, 73, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 0, 0, 184, 316, 317, 318, 319, 320, 321, 322, 323, 185, 325, 326, 0, 0, 0, 0, 329, 330, 0, 0, 73, 0, 93, 750, 0, 0, 0, 73, 0, 0, 230, 0, 0, 73, 0, 73, 0, 0, 0, 0, 0, 489, 73, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 607, 609, 0, 0, 613, 0, 0, 92, 618, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 0, 93, 0, 0, 0, 0, 0, 639, 659, 0, 613, 0, 639, 0, 0, 0, 93, 93, 93, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 0, 0, 329, 330, 0, 656, 0, 0, 93, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 93, 93, 0, 93, 331, 0, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 92, 0, 835, 0, 0, 0, 93, 0, -501, 0, -220, 0, 0, 0, 0, 0, -501, -501, -501, 0, 0, -501, -501, -501, 0, -501, 0, 0, 0, 0, 0, 0, 0, 0, 0, -501, 0, 0, 0, 93, 0, 0, 0, 0, -501, -501, 93, -501, -501, -501, -501, -501, 93, 0, 93, 0, 0, 0, 0, 92, 0, 93, 0, 700, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 92, 92, 717, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -501, 0, 0, 0, 0, 0, 724, 0, 0, 0, 727, 0, 0, 0, 728, 0, 0, 0, 0, 92, 0, 0, 609, 0, -501, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 92, 0, 92, 0, 0, -501, 0, 0, -501, -501, 0, 0, 247, 0, -501, -501, 0, 0, 0, 0, 755, 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 775, 0, 0, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 92, 0, 0, 0, 0, 0, 92, 0, 92, 0, 0, 0, 0, 0, 0, 92, 0, 0, 0, 0, 639, 0, 0, 0, 0, 0, 0, 613, 0, 0, 0, 0, 0, 0, 613, 0, 0, 0, 0, 0, 0, 0, 0, 0, -478, -478, -478, 0, -478, 0, 0, 0, -478, -478, 0, 0, 818, -478, 0, -478, -478, -478, -478, -478, -478, -478, 0, -478, 0, 0, -478, -478, -478, -478, -478, -478, -478, 0, 0, 0, 0, 0, 0, 0, 0, 0, -478, 0, 0, -478, -478, -478, -478, -478, -478, -478, -478, -478, -478, 839, -478, -478, 0, -478, -478, 0, 0, 0, 847, 0, 0, 0, 0, 852, 0, 0, 0, 0, 0, 0, 0, 0, 0, -478, 0, 0, -478, -478, 0, -478, -478, 0, -478, -478, -478, -478, -478, -478, -478, -478, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 609, 0, 613, 0, 0, 0, 0, -478, -478, -478, 0, -478, 0, 0, 0, 0, 0, 0, 0, -478, 0, 0, 885, 0, 888, 0, 0, 0, -501, 4, 0, 5, 6, 7, 8, 9, 0, 0, 613, 10, 11, 0, 0, 893, 12, 0, 13, 14, 15, 16, 17, 18, 19, 0, 0, 0, 0, 20, 21, 22, 23, 24, 25, 26, 0, 0, 27, 0, 0, 0, 0, 0, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46, 0, 0, 47, 48, 0, 49, 50, 0, 51, 0, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -485, 0, 0, 0, 0, 58, 59, 60, -485, -485, -485, 0, 0, 0, -485, -485, 0, -485, 0, -501, -501, 0, 0, 659, 0, 0, -485, 0, 0, 0, 0, 0, 0, 0, 0, 0, -485, -485, 0, -485, -485, -485, -485, -485, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 0, 0, 329, 330, 0, 0, 0, -485, -485, -485, -485, -485, -485, -485, -485, -485, -485, -485, -485, -485, 0, 0, -485, -485, -485, 0, 661, 331, 0, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 0, 0, 0, 0, 0, 0, 0, -87, -485, 0, -485, -485, -485, -485, -485, -485, -485, -485, -485, -485, 0, 0, 0, -283, -485, -485, -485, 0, -485, -485, -79, -283, -283, -283, -485, -485, 0, -283, -283, 0, -283, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -283, -283, 0, -283, -283, -283, -283, -283, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 0, 0, 329, 330, 0, 0, 0, -283, -283, -283, -283, -283, -283, -283, -283, -283, -283, -283, -283, -283, 0, 0, -283, -283, -283, 0, 662, 331, 660, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 0, 0, 0, 0, 0, 0, 0, -89, -283, 0, -283, -283, -283, -283, -283, -283, -283, -283, -283, -283, 0, 0, 0, 0, 0, -283, -283, 0, -283, -283, -81, 0, 0, 0, -283, -283, 4, 0, 5, 6, 7, 8, 9, -501, -501, -501, 10, 11, 0, 0, -501, 12, 0, 13, 14, 15, 16, 17, 18, 19, 0, 0, 0, 0, 20, 21, 22, 23, 24, 25, 26, 0, 0, 27, 0, 0, 0, 0, 0, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46, 0, 0, 47, 48, 0, 49, 50, 0, 51, 0, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58, 59, 60, 0, 0, 0, 0, 4, 0, 5, 6, 7, 8, 9, -501, -501, -501, 10, 11, 0, -501, -501, 12, 0, 13, 14, 15, 16, 17, 18, 19, 0, 0, 0, 0, 20, 21, 22, 23, 24, 25, 26, 0, 0, 27, 0, 0, 0, 0, 0, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46, 0, 0, 47, 48, 0, 49, 50, 0, 51, 0, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58, 59, 60, 0, 0, 0, 0, 4, 0, 5, 6, 7, 8, 9, -501, -501, -501, 10, 11, 0, 0, -501, 12, -501, 13, 14, 15, 16, 17, 18, 19, 0, 0, 0, 0, 20, 21, 22, 23, 24, 25, 26, 0, 0, 27, 0, 0, 0, 0, 0, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46, 0, 0, 47, 48, 0, 49, 50, 0, 51, 0, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58, 59, 60, 0, 0, 0, 0, 4, 0, 5, 6, 7, 8, 9, -501, -501, -501, 10, 11, 0, 0, -501, 12, 0, 13, 14, 15, 16, 17, 18, 19, 0, 0, 0, 0, 20, 21, 22, 23, 24, 25, 26, 0, 0, 27, 0, 0, 0, 0, 0, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46, 0, 0, 47, 48, 0, 49, 50, 0, 51, 0, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 4, 0, 5, 6, 7, 8, 9, 0, -501, -501, 10, 11, 58, 59, 60, 12, 0, 13, 14, 15, 16, 17, 18, 19, 0, 0, -501, -501, 20, 21, 22, 23, 24, 25, 26, 0, 0, 27, 0, 0, 0, 0, 0, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46, 0, 0, 47, 48, 0, 49, 50, 0, 51, 0, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 4, 0, 5, 6, 7, 8, 9, 0, 0, 0, 10, 11, 58, 59, 60, 12, 0, 13, 14, 15, 16, 17, 18, 19, 0, 0, -501, -501, 20, 21, 22, 23, 24, 25, 26, 0, 0, 27, 0, 0, 0, 0, 0, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46, 0, 0, 261, 48, 0, 49, 50, 0, 51, 0, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58, 59, 60, 0, 0, 0, 0, 0, 0, -501, 0, 0, 0, 0, -501, -501, 4, 0, 5, 6, 7, 8, 9, 0, 0, 0, 10, 11, 0, 0, 0, 12, 0, 13, 14, 15, 16, 17, 18, 19, 0, 0, 0, 0, 20, 21, 22, 23, 24, 25, 26, 0, 0, 27, 0, 0, 0, 0, 0, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46, 0, 0, 47, 48, 0, 49, 50, 0, 51, 0, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58, 59, 60, 0, 0, 0, 0, 0, 0, -501, 0, 0, 0, 0, -501, -501, 4, 0, 5, 6, 7, 8, 9, 0, 0, 0, 10, 11, 0, 0, 0, 12, 0, 13, 14, 15, 16, 17, 18, 19, 0, 0, 0, 0, 20, 21, 22, 23, 24, 25, 26, 0, 0, 27, 0, 0, 0, 0, 0, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46, 0, 0, 47, 48, 0, 49, 50, 0, 51, 0, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58, 59, 60, 0, 0, -501, 0, 4, 0, 5, 6, 7, 8, 9, -501, -501, -501, 10, 11, 0, 0, 0, 12, 0, 13, 14, 15, 16, 17, 18, 19, 0, 0, 0, 0, 20, 21, 22, 23, 24, 25, 26, 0, 0, 27, 0, 0, 0, 0, 0, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46, 0, 0, 47, 48, 0, 49, 50, 0, 51, 0, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 8, 9, 0, 0, 0, 10, 11, 58, 59, 60, 12, 0, 13, 14, 15, 16, 17, 18, 19, 0, 0, -501, -501, 20, 21, 22, 23, 24, 25, 26, 0, 0, 27, 0, 0, 0, 0, 0, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46, 0, 0, 47, 48, 0, 49, 50, 0, 51, 0, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 0, 9, 0, 0, 0, 10, 11, 58, 59, 60, 12, 0, 13, 14, 15, 16, 17, 18, 19, 0, 0, 0, 395, 20, 21, 22, 23, 24, 25, 26, 0, 0, 27, 0, 0, 0, 0, 0, 0, 29, 0, 0, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 207, 0, 0, 107, 48, 0, 49, 50, 0, 0, 0, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58, 59, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 216, 217, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 0, 0, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 0, 0, 0, 0, 0, 151, 152, 153, 154, 155, 156, 157, 158, 36, 37, 159, 39, 0, 0, 0, 0, 0, 0, 160, 161, 162, 163, 164, 165, 0, 166, 167, 0, 0, 168, 0, 0, 0, 169, 170, 171, 172, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 173, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 0, 0, 184, 0, 0, -480, -480, -480, 0, -480, 0, 185, 186, -480, -480, 0, 0, 0, -480, 0, -480, -480, -480, -480, -480, -480, -480, 0, -480, 0, 0, -480, -480, -480, -480, -480, -480, -480, 0, 0, 0, 0, 0, 0, 0, 0, 0, -480, 0, 0, -480, -480, -480, -480, -480, -480, -480, -480, -480, -480, 0, -480, -480, 0, -480, -480, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -480, 0, 0, -480, -480, 0, -480, -480, 0, -480, -480, -480, -480, -480, -480, -480, -480, 0, 0, 0, 0, 0, 0, 0, -479, -479, -479, 0, -479, 0, 0, 0, -479, -479, -480, -480, -480, -479, -480, -479, -479, -479, -479, -479, -479, -479, -480, -479, 0, 0, -479, -479, -479, -479, -479, -479, -479, 0, 0, 0, 0, 0, 0, 0, 0, 0, -479, 0, 0, -479, -479, -479, -479, -479, -479, -479, -479, -479, -479, 0, -479, -479, 0, -479, -479, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -479, 0, 0, -479, -479, 0, -479, -479, 0, -479, -479, -479, -479, -479, -479, -479, -479, 0, 0, 0, 0, 0, 0, 0, -481, -481, -481, 0, -481, 0, 0, 0, -481, -481, -479, -479, -479, -481, -479, -481, -481, -481, -481, -481, -481, -481, -479, 0, 0, 0, -481, -481, -481, -481, -481, -481, -481, 0, 0, 0, 0, 0, 0, 0, 0, 0, -481, 0, 0, -481, -481, -481, -481, -481, -481, -481, -481, -481, -481, 0, -481, -481, 0, -481, -481, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -481, 710, 0, -481, -481, 0, -481, -481, 0, -481, -481, -481, -481, -481, -481, -481, -481, 0, 0, 0, 0, -87, 0, 0, -482, -482, -482, 0, -482, 0, 0, 0, -482, -482, -481, -481, -481, -482, 0, -482, -482, -482, -482, -482, -482, -482, -481, 0, 0, 0, -482, -482, -482, -482, -482, -482, -482, 0, 0, 0, 0, 0, 0, 0, 0, 0, -482, 0, 0, -482, -482, -482, -482, -482, -482, -482, -482, -482, -482, 0, -482, -482, 0, -482, -482, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -482, 662, 0, -482, -482, 0, -482, -482, 0, -482, -482, -482, -482, -482, -482, -482, -482, 0, 0, 0, 0, -89, 0, 0, -254, -254, -254, 0, -254, 0, 0, 0, -254, -254, -482, -482, -482, -254, 0, -254, -254, -254, -254, -254, -254, -254, -482, 0, 0, 0, -254, -254, -254, -254, -254, -254, -254, 0, 0, 0, 0, 0, 0, 0, 0, 0, -254, 0, 0, -254, -254, -254, -254, -254, -254, -254, -254, -254, -254, 0, -254, -254, 0, -254, -254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -254, 0, 0, -254, -254, 0, -254, -254, 0, -254, -254, -254, -254, -254, -254, -254, -254, 0, 0, 0, 0, 0, 0, 0, -254, -254, -254, 0, -254, 0, 0, 0, -254, -254, -254, -254, -254, -254, 0, -254, -254, -254, -254, -254, -254, -254, 244, 0, 0, 0, -254, -254, -254, -254, -254, -254, -254, 0, 0, 0, 0, 0, 0, 0, 0, 0, -254, 0, 0, -254, -254, -254, -254, -254, -254, -254, -254, -254, -254, 0, -254, -254, 0, -254, -254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -254, 0, 0, -254, -254, 0, -254, -254, 0, -254, -254, -254, -254, -254, -254, -254, -254, 0, 0, 0, 0, 0, 0, 0, -483, -483, -483, 0, -483, 0, 0, 0, -483, -483, -254, -254, -254, -483, 0, -483, -483, -483, -483, -483, -483, -483, 247, 0, 0, 0, -483, -483, -483, -483, -483, -483, -483, 0, 0, 0, 0, 0, 0, 0, 0, 0, -483, 0, 0, -483, -483, -483, -483, -483, -483, -483, -483, -483, -483, 0, -483, -483, 0, -483, -483, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -483, 0, 0, -483, -483, 0, -483, -483, 0, -483, -483, -483, -483, -483, -483, -483, -483, 0, 0, 0, 0, 0, 0, 0, -484, -484, -484, 0, -484, 0, 0, 0, -484, -484, -483, -483, -483, -484, 0, -484, -484, -484, -484, -484, -484, -484, -483, 0, 0, 0, -484, -484, -484, -484, -484, -484, -484, 0, 0, 0, 0, 0, 0, 0, 0, 0, -484, 0, 0, -484, -484, -484, -484, -484, -484, -484, -484, -484, -484, 0, -484, -484, 0, -484, -484, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -484, 0, 0, -484, -484, 0, -484, -484, 0, -484, -484, -484, -484, -484, -484, -484, -484, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -484, -484, -484, 0, 0, 0, 0, 0, 0, 0, 0, 0, -484, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 0, 0, 141, 142, 143, 192, 193, 194, 195, 148, 149, 150, 0, 0, 0, 0, 0, 151, 152, 153, 154, 196, 197, 198, 158, 281, 282, 199, 283, 0, 0, 0, 0, 0, 0, 160, 161, 162, 163, 164, 165, 0, 166, 167, 0, 0, 168, 0, 0, 0, 169, 170, 171, 172, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 173, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 0, 0, 184, 0, 0, 0, 0, 0, 0, 0, 0, 185, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 0, 0, 141, 142, 143, 192, 193, 194, 195, 148, 149, 150, 0, 0, 0, 0, 0, 151, 152, 153, 154, 196, 197, 198, 158, 251, 0, 199, 0, 0, 0, 0, 0, 0, 0, 160, 161, 162, 163, 164, 165, 0, 166, 167, 0, 0, 168, 0, 0, 0, 169, 170, 171, 172, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 173, 0, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 0, 0, 184, 0, 0, 0, 0, 0, 0, 0, 0, 185, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 0, 0, 141, 142, 143, 192, 193, 194, 195, 148, 149, 150, 0, 0, 0, 0, 0, 151, 152, 153, 154, 196, 197, 198, 158, 0, 0, 199, 0, 0, 0, 0, 0, 0, 0, 160, 161, 162, 163, 164, 165, 0, 166, 167, 0, 0, 168, 0, 0, 0, 169, 170, 171, 172, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 173, 0, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 0, 0, 184, 0, 0, 0, 0, 0, 0, 0, 0, 185, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 0, 0, 141, 142, 143, 192, 193, 194, 195, 148, 149, 150, 0, 0, 0, 0, 0, 151, 152, 153, 154, 196, 197, 198, 158, 0, 0, 199, 0, 0, 0, 0, 0, 0, 0, 160, 161, 162, 163, 164, 165, 0, 166, 167, 0, 0, 168, 0, 0, 0, 169, 170, 171, 172, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 173, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 0, 0, 184, 0, 5, 6, 7, 0, 9, 0, 0, 185, 10, 11, 0, 0, 0, 12, 0, 13, 14, 15, 97, 98, 18, 19, 0, 0, 0, 0, 99, 21, 22, 23, 24, 25, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 207, 0, 0, 107, 48, 0, 49, 50, 0, 231, 232, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 8, 9, 58, 233, 60, 10, 11, 0, 0, 0, 12, 411, 13, 14, 15, 16, 17, 18, 19, 0, 0, 0, 0, 20, 21, 22, 23, 24, 25, 26, 0, 0, 27, 0, 0, 0, 0, 0, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 46, 0, 0, 47, 48, 0, 49, 50, 0, 51, 0, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 0, 9, 0, 0, 0, 10, 11, 58, 59, 60, 12, 0, 13, 14, 15, 16, 17, 18, 19, 0, 0, 0, 0, 20, 21, 22, 23, 24, 25, 26, 0, 0, 27, 0, 0, 0, 0, 0, 0, 29, 0, 0, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 207, 0, 0, 107, 48, 0, 49, 50, 0, 616, 232, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 0, 9, 0, 0, 0, 10, 11, 58, 59, 60, 12, 0, 13, 14, 15, 97, 98, 18, 19, 0, 0, 0, 0, 99, 21, 22, 23, 24, 25, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 207, 0, 0, 107, 48, 0, 49, 50, 0, 231, 232, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 0, 9, 0, 0, 0, 10, 11, 58, 233, 60, 12, 0, 13, 14, 15, 97, 98, 18, 19, 0, 0, 0, 0, 99, 21, 22, 23, 24, 25, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 207, 0, 0, 107, 413, 0, 49, 50, 0, 231, 232, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 0, 9, 0, 0, 0, 10, 11, 58, 233, 60, 12, 0, 13, 14, 15, 97, 98, 18, 19, 0, 0, 0, 0, 99, 100, 101, 23, 24, 25, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 207, 0, 0, 107, 48, 0, 49, 50, 0, 608, 232, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 0, 9, 0, 0, 0, 10, 11, 58, 233, 60, 12, 0, 13, 14, 15, 97, 98, 18, 19, 0, 0, 0, 0, 99, 100, 101, 23, 24, 25, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 207, 0, 0, 107, 48, 0, 49, 50, 0, 612, 232, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 0, 9, 0, 0, 0, 10, 11, 58, 233, 60, 12, 0, 13, 14, 15, 97, 98, 18, 19, 0, 0, 0, 0, 99, 21, 22, 23, 24, 25, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 207, 0, 0, 107, 48, 0, 49, 50, 0, 608, 232, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 0, 9, 0, 0, 0, 10, 11, 58, 233, 60, 12, 0, 13, 14, 15, 97, 98, 18, 19, 0, 0, 0, 0, 99, 100, 101, 23, 24, 25, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 207, 0, 0, 107, 48, 0, 49, 50, 0, 803, 232, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 0, 9, 0, 0, 0, 10, 11, 58, 233, 60, 12, 0, 13, 14, 15, 97, 98, 18, 19, 0, 0, 0, 0, 99, 100, 101, 23, 24, 25, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 207, 0, 0, 107, 48, 0, 49, 50, 0, 806, 232, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 0, 9, 0, 0, 0, 10, 11, 58, 233, 60, 12, 0, 13, 14, 15, 97, 98, 18, 19, 0, 0, 0, 0, 99, 100, 101, 23, 24, 25, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 207, 0, 0, 107, 48, 0, 49, 50, 0, 811, 232, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 0, 9, 0, 0, 0, 10, 11, 58, 233, 60, 12, 0, 13, 14, 15, 97, 98, 18, 19, 0, 0, 0, 0, 99, 100, 101, 23, 24, 25, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 207, 0, 0, 107, 48, 0, 49, 50, 0, 870, 232, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 0, 9, 0, 0, 0, 10, 11, 58, 233, 60, 12, 0, 13, 14, 15, 97, 98, 18, 19, 0, 0, 0, 0, 99, 100, 101, 23, 24, 25, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 207, 0, 0, 107, 48, 0, 49, 50, 0, 872, 232, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 0, 9, 0, 0, 0, 10, 11, 58, 233, 60, 12, 0, 13, 14, 15, 97, 98, 18, 19, 0, 0, 0, 0, 99, 100, 101, 23, 24, 25, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 207, 0, 0, 107, 48, 0, 49, 50, 0, 891, 232, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 0, 9, 0, 0, 0, 10, 11, 58, 233, 60, 12, 0, 13, 14, 15, 16, 17, 18, 19, 0, 0, 0, 0, 20, 21, 22, 23, 24, 25, 26, 0, 0, 27, 0, 0, 0, 0, 0, 0, 29, 0, 0, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 207, 0, 0, 107, 48, 0, 49, 50, 0, 0, 0, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 0, 9, 0, 0, 0, 10, 11, 58, 59, 60, 12, 0, 13, 14, 15, 97, 98, 18, 19, 0, 0, 0, 0, 99, 21, 22, 23, 24, 25, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 207, 0, 0, 107, 48, 0, 49, 50, 0, 266, 0, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 0, 9, 0, 0, 0, 10, 11, 58, 233, 60, 12, 0, 13, 14, 15, 16, 17, 18, 19, 0, 0, 0, 0, 20, 21, 22, 23, 24, 25, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 207, 0, 0, 107, 48, 0, 49, 50, 0, 479, 0, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 0, 9, 0, 0, 0, 10, 11, 58, 233, 60, 12, 0, 13, 14, 15, 97, 98, 18, 19, 0, 0, 0, 0, 99, 100, 101, 23, 24, 25, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 207, 0, 0, 107, 48, 0, 49, 50, 0, 590, 0, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 0, 9, 0, 0, 0, 10, 11, 58, 233, 60, 12, 0, 13, 14, 15, 97, 98, 18, 19, 0, 0, 0, 0, 99, 100, 101, 23, 24, 25, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 207, 0, 0, 107, 48, 0, 49, 50, 0, 638, 0, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 0, 9, 0, 0, 0, 10, 11, 58, 233, 60, 12, 0, 13, 14, 15, 97, 98, 18, 19, 0, 0, 0, 0, 99, 100, 101, 23, 24, 25, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 207, 0, 0, 107, 48, 0, 49, 50, 0, 479, 0, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 0, 9, 0, 0, 0, 10, 11, 58, 233, 60, 12, 0, 13, 14, 15, 97, 98, 18, 19, 0, 0, 0, 0, 99, 100, 101, 23, 24, 25, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 207, 0, 0, 107, 48, 0, 49, 50, 0, 754, 0, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 0, 9, 0, 0, 0, 10, 11, 58, 233, 60, 12, 0, 13, 14, 15, 97, 98, 18, 19, 0, 0, 0, 0, 99, 100, 101, 23, 24, 25, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 207, 0, 0, 107, 48, 0, 49, 50, 0, 797, 0, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 0, 9, 0, 0, 0, 10, 11, 58, 233, 60, 12, 0, 13, 14, 15, 97, 98, 18, 19, 0, 0, 0, 0, 99, 100, 101, 23, 24, 25, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 207, 0, 0, 107, 48, 0, 49, 50, 0, 0, 0, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 0, 9, 0, 0, 0, 10, 11, 58, 233, 60, 12, 0, 13, 14, 15, 16, 17, 18, 19, 0, 0, 0, 0, 20, 21, 22, 23, 24, 25, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 44, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 207, 0, 0, 107, 48, 0, 49, 50, 0, 0, 0, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 0, 9, 0, 0, 0, 10, 11, 58, 233, 60, 12, 0, 13, 14, 15, 97, 98, 18, 19, 0, 0, 0, 0, 99, 100, 101, 23, 24, 25, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 102, 0, 0, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 0, 0, 47, 48, 0, 49, 50, 0, 51, 0, 52, 53, 54, 55, 56, 57, 0, 0, 0, 5, 6, 7, 0, 9, 0, 751, 0, 10, 11, 0, 0, 0, 12, 108, 13, 14, 15, 97, 98, 18, 19, 0, 0, 0, 0, 99, 100, 101, 23, 24, 25, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 102, 0, 0, 32, 33, 103, 35, 36, 37, 104, 39, 40, 41, 0, 42, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 105, 0, 0, 106, 0, 0, 107, 48, 0, 49, 50, 0, 0, 0, 52, 53, 54, 55, 56, 57, 0, 0, 0, 5, 6, 7, 0, 9, 0, 0, 0, 10, 11, 0, 0, 0, 12, 108, 13, 14, 15, 97, 98, 18, 19, 0, 0, 0, 0, 99, 100, 101, 23, 24, 25, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 102, 0, 0, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 0, 0, 47, 48, 0, 49, 50, 0, 51, 0, 52, 53, 54, 55, 56, 57, 0, 0, 0, 5, 6, 7, 0, 9, 0, 0, 0, 10, 11, 0, 0, 0, 12, 108, 13, 14, 15, 97, 98, 18, 19, 0, 0, 0, 0, 99, 100, 101, 23, 24, 25, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 102, 0, 0, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 276, 0, 0, 312, 48, 0, 49, 50, 0, 313, 0, 52, 53, 54, 55, 56, 57, 0, 0, 0, 5, 6, 7, 0, 9, 0, 0, 0, 10, 11, 0, 0, 0, 12, 108, 13, 14, 15, 97, 98, 18, 19, 0, 0, 0, 0, 99, 100, 101, 23, 24, 25, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 102, 0, 0, 32, 33, 103, 35, 36, 37, 104, 39, 40, 41, 0, 42, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 106, 0, 0, 107, 48, 0, 49, 50, 0, 0, 0, 52, 53, 54, 55, 56, 57, 0, 0, 0, 5, 6, 7, 0, 9, 0, 0, 0, 10, 11, 0, 0, 0, 12, 108, 13, 14, 15, 97, 98, 18, 19, 0, 0, 0, 0, 99, 100, 101, 23, 24, 25, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 102, 0, 0, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 276, 0, 0, 107, 48, 0, 49, 50, 0, 0, 0, 52, 53, 54, 55, 56, 57, 0, 0, 0, 5, 6, 7, 0, 9, 0, 0, 0, 10, 11, 0, 0, 0, 12, 108, 13, 14, 15, 97, 98, 18, 19, 0, 0, 0, 0, 99, 100, 101, 23, 24, 25, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 102, 0, 0, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 0, 42, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 831, 0, 0, 107, 48, 0, 49, 50, 0, 0, 0, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, 524, 525, 0, 0, 526, 0, 0, 0, 0, 0, 0, 108, 160, 161, 162, 163, 164, 165, 0, 166, 167, 0, 0, 168, 0, 0, 0, 169, 170, 171, 172, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 173, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 529, 525, 184, 0, 530, 0, 0, 0, 0, 0, 0, 185, 160, 161, 162, 163, 164, 165, 0, 166, 167, 0, 0, 168, 0, 0, 0, 169, 170, 171, 172, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 173, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 545, 518, 184, 0, 546, 0, 0, 0, 0, 0, 0, 185, 160, 161, 162, 163, 164, 165, 0, 166, 167, 0, 0, 168, 0, 0, 0, 169, 170, 171, 172, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 173, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 582, 518, 184, 0, 583, 0, 0, 0, 0, 0, 0, 185, 160, 161, 162, 163, 164, 165, 0, 166, 167, 0, 0, 168, 0, 0, 0, 169, 170, 171, 172, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 173, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 585, 525, 184, 0, 586, 0, 0, 0, 0, 0, 0, 185, 160, 161, 162, 163, 164, 165, 0, 166, 167, 0, 0, 168, 0, 0, 0, 169, 170, 171, 172, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 173, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 599, 518, 184, 0, 600, 0, 0, 0, 0, 0, 0, 185, 160, 161, 162, 163, 164, 165, 0, 166, 167, 0, 0, 168, 0, 0, 0, 169, 170, 171, 172, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 173, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 602, 525, 184, 0, 603, 0, 0, 0, 0, 0, 0, 185, 160, 161, 162, 163, 164, 165, 0, 166, 167, 0, 0, 168, 0, 0, 0, 169, 170, 171, 172, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 173, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 640, 518, 184, 0, 641, 0, 0, 0, 0, 0, 0, 185, 160, 161, 162, 163, 164, 165, 0, 166, 167, 0, 0, 168, 0, 0, 0, 169, 170, 171, 172, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 173, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 643, 525, 184, 0, 644, 0, 0, 0, 0, 0, 0, 185, 160, 161, 162, 163, 164, 165, 0, 166, 167, 0, 0, 168, 0, 0, 0, 169, 170, 171, 172, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 173, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 877, 518, 184, 0, 878, 0, 0, 0, 0, 0, 0, 185, 160, 161, 162, 163, 164, 165, 0, 166, 167, 0, 0, 168, 0, 0, 0, 169, 170, 171, 172, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 173, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 880, 525, 184, 0, 881, 0, 0, 0, 0, 0, 0, 185, 160, 161, 162, 163, 164, 165, 0, 166, 167, 0, 0, 168, 0, 0, 0, 169, 170, 171, 172, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 173, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 0, 0, 184, 0, 0, 0, 0, 0, 0, 0, 0, 185, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 0, 0, 329, 330, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 331, 0, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 0, 253, 329, 330, 0, 0, 0, -220, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 0, 0, 329, 330, 0, 0, 331, 0, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 0, 0, 0, 0, 0, 0, 0, 0, 0, 331, -220, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 605, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 0, 0, 329, 330, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, -502, -502, 0, 0, 329, 330, 0, 0, 0, 0, 0, 0, 331, 0, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 0, 0, 0, 0, 0, 0, 0, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 316, -502, -502, -502, -502, 321, 322, 0, 0, -502, -502, 0, 0, 0, 0, 329, 330, 316, 317, 318, 319, 320, 321, 322, 0, 0, 325, 326, 0, 0, 0, 0, 329, 330, 0, 0, 0, 0, 0, 0, 0, 0, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 0, 0, 0, 0, 0, 0, 0, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341 }; static const yytype_int16 yycheck[] = { 16, 17, 7, 8, 20, 343, 215, 16, 17, 15, 370, 20, 16, 17, 7, 22, 20, 16, 17, 247, 8, 20, 8, 28, 22, 397, 51, 409, 44, 45, 368, 5, 6, 49, 50, 391, 106, 315, 62, 692, 28, 15, 28, 59, 60, 27, 50, 52, 386, 28, 49, 372, 531, 532, 13, 11, 12, 577, 26, 52, 442, 13, 400, 4, 568, 12, 570, 587, 26, 47, 287, 36, 37, 0, 291, 533, 534, 51, 261, 1, 92, 689, 59, 58, 692, 25, 130, 94, 58, 13, 81, 25, 81, 83, 101, 664, 94, 71, 110, 17, 84, 670, 20, 15, 54, 17, 13, 64, 115, 72, 25, 129, 450, 103, 83, 62, 342, 120, 344, 345, 346, 83, 102, 105, 99, 100, 16, 17, 129, 312, 20, 101, 28, 124, 103, 124, 126, 36, 37, 130, 50, 103, 126, 369, 54, 108, 105, 83, 130, 131, 56, 57, 120, 105, 129, 26, 113, 114, 115, 385, 50, 387, 120, 110, 126, 130, 26, 26, 108, 128, 128, 130, 131, 826, 108, 401, 81, 103, 130, 131, 15, 105, 17, 126, 544, 107, 126, 2, 128, 4, 5, 6, 126, 108, 218, 10, 11, 12, 105, 103, 15, 16, 17, 563, 430, 20, 130, 131, 130, 131, 81, 126, 27, 128, 94, 231, 232, 233, 826, 124, 678, 81, 81, 130, 131, 451, 125, 128, 244, 81, 246, 247, 47, 48, 49, 244, 51, 246, 247, 128, 244, 562, 246, 247, 59, 244, 463, 246, 247, 120, 266, 122, 81, 124, 130, 131, 71, 128, 737, 738, 120, 120, 122, 122, 124, 124, 222, 223, 128, 128, 25, 218, 124, 126, 52, 779, 780, 25, 798, 54, 784, 59, 60, 261, 25, 36, 37, 267, 313, 58, 105, 103, 107, 122, 310, 124, 83, 367, 576, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 244, 343, 246, 247, 99, 100, 101, 310, 312, 351, 352, 342, 315, 313, 131, 83, 54, 725, 343, 301, 302, 303, 304, 25, 732, 723, 368, 108, 244, 859, 246, 247, 84, 300, 108, 103, 367, 81, 305, 25, 83, 108, 384, 368, 386, 126, 388, 389, 351, 352, 25, 393, 126, 25, 385, 56, 57, 592, 400, 126, 103, 386, 126, 405, 406, 206, 25, 409, 399, 126, 624, 413, 240, 625, 626, 400, 406, 128, 413, 120, 124, 222, 223, 413, 426, 771, 14, 15, 81, 431, 363, 81, 81, 424, 310, 125, 126, 439, 429, 315, 442, 83, 444, 244, 64, 246, 247, 2, 450, 125, 424, 417, 424, 419, 125, 256, 798, 103, 449, 424, 261, 103, 108, 396, 397, 450, 468, 469, 103, 435, 436, 124, 64, 108, 124, 124, 108, 479, 785, 125, 126, 125, 850, 50, 830, 52, 53, 54, 55, 108, 125, 126, 47, 50, 126, 52, 53, 54, 55, 300, 301, 302, 303, 304, 305, 306, 307, 126, 123, 310, 121, 312, 313, 83, 315, 64, 50, 393, 886, 111, 112, 113, 114, 115, 91, 92, 54, 55, 521, 83, 79, 80, 126, 103, 91, 92, 130, 521, 54, 863, 101, 126, 343, 110, 528, 130, 113, 531, 532, 103, 351, 352, 107, 110, 58, 548, 113, 91, 92, 101, 81, 128, 111, 112, 113, 114, 115, 368, 81, 370, 371, 64, 81, 83, 576, 403, 110, 64, 126, 113, 81, 382, 573, 574, 83, 386, 552, 58, 590, 390, 81, 392, 787, 103, 573, 99, 100, 101, 793, 400, 574, 543, 574, 124, 103, 90, 608, 10, 541, 574, 612, 124, 413, 122, 616, 124, 126, 13, 8, 128, 126, 122, 624, 124, 130, 64, 427, 126, 99, 100, 101, 564, 17, 124, 624, 10, 638, 58, 126, 624, 79, 80, 125, 2, 81, 4, 5, 6, 7, 450, 126, 125, 864, 81, 81, 657, 15, 659, 660, 661, 662, 653, 604, 121, 666, 667, 81, 126, 125, 108, 109, 110, 111, 112, 113, 114, 115, 129, 99, 100, 101, 81, 83, 685, 126, 122, 126, 124, 47, 125, 81, 123, 51, 653, 122, 122, 124, 124, 661, 123, 576, 256, 103, 666, 667, 10, 261, 122, 710, 124, 712, 713, 71, 81, 103, 126, 718, 126, 81, 50, 686, 83, 122, 725, 124, 126, 81, 729, 126, 50, 732, 122, 15, 124, 729, 81, 123, 52, 53, 729, 55, 103, 543, 544, 59, 60, 58, 680, 107, 61, 10, 270, 754, 272, 122, 274, 124, 312, 10, 122, 762, 124, 563, 92, 126, 707, 126, 122, 126, 124, 736, 2, 123, 4, 126, 125, 122, 743, 124, 10, 11, 12, 125, 611, 125, 16, 17, 99, 100, 20, 723, 121, 64, 795, 622, 797, 27, 125, 123, 762, 10, 803, 125, 604, 806, 10, 84, 79, 80, 811, 39, 40, 41, 42, 43, 9, 47, 48, 49, 49, 50, 10, 10, 624, 125, 121, 786, 382, 59, 630, 125, 108, 126, 126, 10, 390, 10, 392, 110, 111, 112, 113, 114, 115, 645, 126, 848, 206, 850, 10, 126, 125, 653, 729, 10, 121, 10, 54, 848, 121, 661, 863, 10, 10, 71, 666, 667, 126, 870, 123, 872, 862, 427, 6, 105, 653, 107, 78, 863, 81, 866, 7, 865, 785, 886, 562, 689, 689, -1, 891, 62, -1, -1, -1, -1, -1, 724, 256, -1, 727, 728, -1, 261, 704, -1, -1, 707, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 719, 720, 721, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 300, -1, 2, -1, 4, 305, -1, -1, -1, -1, -1, 753, 312, 313, -1, 15, -1, -1, -1, -1, 762, -1, -1, -1, -1, -1, 768, 769, -1, 771, -1, -1, -1, -1, 206, -1, -1, -1, -1, 808, 809, 10, 11, 12, 786, -1, -1, 47, 790, -1, 222, 223, -1, -1, -1, -1, -1, -1, 27, -1, -1, -1, -1, -1, -1, -1, -1, -1, 239, 240, 370, -1, 244, -1, 246, 247, 247, 846, 847, 48, -1, 823, 382, 852, 256, -1, -1, -1, 830, 261, 390, -1, 392, -1, 836, -1, 838, -1, -1, -1, -1, -1, 871, 845, -1, -1, -1, 107, -1, -1, -1, -1, -1, -1, -1, -1, 885, -1, -1, 888, -1, 863, -1, -1, 893, -1, -1, 427, 300, 301, 302, 303, 304, 305, 306, 307, 105, -1, 310, -1, 312, -1, -1, 315, -1, -1, -1, -1, -1, 645, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 343, 343, -1, -1, -1, -1, -1, -1, 351, 352, -1, 2, -1, 4, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, 15, 368, 368, 370, 371, -1, -1, -1, -1, -1, -1, 206, 378, 704, -1, 382, -1, -1, -1, 386, 386, -1, -1, 390, -1, 392, -1, -1, 719, 720, 721, -1, 47, 400, 400, -1, 51, 403, -1, -1, -1, -1, -1, -1, -1, -1, 413, -1, -1, 544, -1, -1, 418, -1, -1, -1, 71, -1, 222, 223, 427, 256, 753, -1, -1, -1, 261, -1, 563, -1, -1, -1, -1, -1, -1, -1, -1, 768, 769, -1, -1, -1, -1, 450, 450, -1, -1, -1, -1, -1, -1, -1, 107, -1, -1, -1, -1, -1, -1, 790, -1, -1, -1, -1, -1, 300, -1, -1, -1, -1, 305, -1, 478, -1, -1, -1, -1, 312, -1, -1, -1, -1, -1, -1, -1, -1, 2, -1, 4, -1, -1, -1, 823, -1, -1, -1, -1, 301, 302, 303, 304, -1, 306, 307, -1, 836, -1, 838, -1, -1, 645, -1, -1, -1, 845, -1, -1, -1, 653, -1, -1, -1, 528, -1, -1, 531, 532, 533, 534, -1, -1, 47, -1, -1, -1, 370, 543, 544, -1, -1, -1, -1, -1, -1, 550, -1, -1, 382, -1, -1, -1, 206, -1, -1, -1, 390, 563, 392, -1, -1, 566, -1, 568, -1, 570, -1, -1, 371, -1, 704, 576, 577, -1, 579, -1, -1, -1, -1, -1, -1, -1, 587, -1, -1, 719, 720, 721, -1, -1, -1, -1, 107, 427, -1, -1, -1, -1, 604, -1, -1, -1, 256, -1, -1, -1, 611, 261, -1, -1, 413, -1, -1, -1, -1, -1, -1, 622, 624, 753, -1, -1, -1, -1, 630, -1, -1, -1, -1, -1, -1, -1, -1, -1, 768, 769, -1, 771, -1, 645, -1, -1, -1, -1, -1, -1, 300, -1, -1, -1, -1, 305, 786, -1, -1, 661, 790, -1, 312, 313, 666, 667, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 678, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 823, -1, 206, -1, -1, -1, -1, 830, 702, 704, -1, 705, 707, 836, -1, 838, -1, -1, -1, -1, -1, 544, 845, -1, 719, 720, 721, 370, -1, -1, 724, -1, -1, 727, 728, -1, -1, -1, -1, 382, 563, -1, -1, 737, 738, -1, -1, 390, -1, 392, -1, 543, 256, -1, -1, -1, -1, 261, 753, -1, -1, -1, -1, -1, -1, -1, -1, 762, -1, -1, -1, -1, -1, 768, 769, -1, 771, -1, -1, -1, -1, -1, -1, -1, 427, 779, 780, -1, -1, -1, 784, -1, -1, -1, -1, 790, 300, -1, -1, -1, -1, 305, -1, -1, 798, -1, -1, -1, 312, -1, -1, -1, 604, -1, 808, 809, -1, -1, -1, -1, -1, -1, 645, -1, -1, -1, -1, -1, 823, -1, 653, -1, -1, -1, -1, 830, -1, -1, 630, -1, -1, 836, -1, 838, -1, -1, -1, -1, -1, -1, 845, -1, 846, 847, -1, -1, -1, -1, 852, 231, 232, -1, -1, -1, -1, 859, 370, -1, 863, 863, -1, 865, 866, -1, -1, -1, -1, 871, 382, -1, -1, 704, -1, -1, -1, -1, 390, -1, 392, -1, -1, 885, -1, -1, 888, -1, 719, 720, 721, 893, -1, 544, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 50, 51, -1, 707, 54, -1, -1, -1, 563, -1, -1, 427, 62, 63, 64, 65, 66, 67, 753, 69, 70, -1, -1, 73, -1, -1, 310, 77, 78, 79, 80, 315, -1, 768, 769, -1, 771, -1, -1, -1, -1, 91, -1, -1, -1, -1, -1, -1, -1, -1, -1, 786, -1, -1, -1, 790, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, -1, -1, 118, 64, 65, 66, 67, 68, 69, 70, 71, 127, 73, 74, -1, -1, -1, -1, 79, 80, -1, -1, 823, -1, 645, 646, -1, -1, -1, 830, -1, -1, 653, -1, -1, 836, -1, 838, -1, -1, -1, -1, -1, 393, 845, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 405, 406, -1, -1, 409, -1, -1, 544, 413, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 563, -1, 704, -1, -1, -1, -1, -1, 439, 43, -1, 442, -1, 444, -1, -1, -1, 719, 720, 721, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, -1, -1, 79, 80, -1, 479, -1, -1, 753, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 768, 769, -1, 771, 104, -1, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 645, -1, 786, -1, -1, -1, 790, -1, 0, -1, 126, -1, -1, -1, -1, -1, 8, 9, 10, -1, -1, 13, 14, 15, -1, 17, -1, -1, -1, -1, -1, -1, -1, -1, -1, 27, -1, -1, -1, 823, -1, -1, -1, -1, 36, 37, 830, 39, 40, 41, 42, 43, 836, -1, 838, -1, -1, -1, -1, 704, -1, 845, -1, 576, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 719, 720, 721, 590, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 81, -1, -1, -1, -1, -1, 608, -1, -1, -1, 612, -1, -1, -1, 616, -1, -1, -1, -1, 753, -1, -1, 624, -1, 105, -1, -1, -1, -1, -1, -1, -1, -1, -1, 768, 769, -1, 771, -1, -1, 121, -1, -1, 124, 125, -1, -1, 128, -1, 130, 131, -1, -1, -1, -1, 657, 790, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 685, -1, -1, -1, -1, -1, 823, -1, -1, -1, -1, -1, -1, 830, -1, -1, -1, -1, -1, 836, -1, 838, -1, -1, -1, -1, -1, -1, 845, -1, -1, -1, -1, 718, -1, -1, -1, -1, -1, -1, 725, -1, -1, -1, -1, -1, -1, 732, -1, -1, -1, -1, -1, -1, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, -1, -1, 754, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, 26, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 797, 59, 60, -1, 62, 63, -1, -1, -1, 806, -1, -1, -1, -1, 811, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, 92, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 848, -1, 850, -1, -1, -1, -1, 116, 117, 118, -1, 120, -1, -1, -1, -1, -1, -1, -1, 128, -1, -1, 870, -1, 872, -1, -1, -1, 0, 1, -1, 3, 4, 5, 6, 7, -1, -1, 886, 11, 12, -1, -1, 891, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, 38, -1, -1, -1, -1, -1, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, -1, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, -1, -1, -1, -1, 116, 117, 118, 8, 9, 10, -1, -1, -1, 14, 15, -1, 17, -1, 130, 131, -1, -1, 43, -1, -1, 26, -1, -1, -1, -1, -1, -1, -1, -1, -1, 36, 37, -1, 39, 40, 41, 42, 43, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, -1, -1, 79, 80, -1, -1, -1, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, -1, -1, 79, 80, 81, -1, 83, 104, -1, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, -1, -1, -1, -1, -1, -1, -1, 103, 104, -1, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, -1, -1, -1, 0, 120, 121, 122, -1, 124, 125, 126, 8, 9, 10, 130, 131, -1, 14, 15, -1, 17, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 36, 37, -1, 39, 40, 41, 42, 43, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, -1, -1, 79, 80, -1, -1, -1, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, -1, -1, 79, 80, 81, -1, 83, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, -1, -1, -1, -1, -1, -1, -1, 103, 104, -1, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, -1, -1, -1, -1, -1, 121, 122, -1, 124, 125, 126, -1, -1, -1, 130, 131, 1, -1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, -1, -1, 15, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, 38, -1, -1, -1, -1, -1, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, -1, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 116, 117, 118, -1, -1, -1, -1, 1, -1, 3, 4, 5, 6, 7, 130, 131, 10, 11, 12, -1, 14, 15, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, 38, -1, -1, -1, -1, -1, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, -1, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 116, 117, 118, -1, -1, -1, -1, 1, -1, 3, 4, 5, 6, 7, 130, 131, 10, 11, 12, -1, -1, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, 38, -1, -1, -1, -1, -1, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, -1, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 116, 117, 118, -1, -1, -1, -1, 1, -1, 3, 4, 5, 6, 7, 130, 131, 10, 11, 12, -1, -1, 15, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, 38, -1, -1, -1, -1, -1, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, -1, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, 1, -1, 3, 4, 5, 6, 7, -1, 9, 10, 11, 12, 116, 117, 118, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, 130, 131, 29, 30, 31, 32, 33, 34, 35, -1, -1, 38, -1, -1, -1, -1, -1, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, -1, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, 1, -1, 3, 4, 5, 6, 7, -1, -1, -1, 11, 12, 116, 117, 118, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, 130, 131, 29, 30, 31, 32, 33, 34, 35, -1, -1, 38, -1, -1, -1, -1, -1, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, -1, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 116, 117, 118, -1, -1, -1, -1, -1, -1, 125, -1, -1, -1, -1, 130, 131, 1, -1, 3, 4, 5, 6, 7, -1, -1, -1, 11, 12, -1, -1, -1, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, 38, -1, -1, -1, -1, -1, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, -1, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 116, 117, 118, -1, -1, -1, -1, -1, -1, 125, -1, -1, -1, -1, 130, 131, 1, -1, 3, 4, 5, 6, 7, -1, -1, -1, 11, 12, -1, -1, -1, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, 38, -1, -1, -1, -1, -1, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, -1, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 116, 117, 118, -1, -1, 121, -1, 1, -1, 3, 4, 5, 6, 7, 130, 131, 10, 11, 12, -1, -1, -1, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, 38, -1, -1, -1, -1, -1, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, -1, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, 3, 4, 5, 6, 7, -1, -1, -1, 11, 12, 116, 117, 118, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, 130, 131, 29, 30, 31, 32, 33, 34, 35, -1, -1, 38, -1, -1, -1, -1, -1, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, -1, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, 116, 117, 118, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, 131, 29, 30, 31, 32, 33, 34, 35, -1, -1, 38, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, -1, -1, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 116, 117, 118, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 130, 131, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, -1, -1, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, -1, -1, -1, -1, -1, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, -1, -1, -1, -1, -1, -1, 62, 63, 64, 65, 66, 67, -1, 69, 70, -1, -1, 73, -1, -1, -1, 77, 78, 79, 80, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 91, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, -1, -1, 118, -1, -1, 3, 4, 5, -1, 7, -1, 127, 128, 11, 12, -1, -1, -1, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, 26, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, 92, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, 116, 117, 118, 16, 120, 18, 19, 20, 21, 22, 23, 24, 128, 26, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, 92, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, 116, 117, 118, 16, 120, 18, 19, 20, 21, 22, 23, 24, 128, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, 83, -1, 85, 86, -1, 88, 89, -1, 91, 92, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, 103, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, 116, 117, 118, 16, -1, 18, 19, 20, 21, 22, 23, 24, 128, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, 83, -1, 85, 86, -1, 88, 89, -1, 91, 92, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, 103, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, 116, 117, 118, 16, -1, 18, 19, 20, 21, 22, 23, 24, 128, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, 92, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, 116, 117, 118, 16, -1, 18, 19, 20, 21, 22, 23, 24, 128, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, 92, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, 116, 117, 118, 16, -1, 18, 19, 20, 21, 22, 23, 24, 128, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, 92, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, 116, 117, 118, 16, -1, 18, 19, 20, 21, 22, 23, 24, 128, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, 92, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 116, 117, 118, -1, -1, -1, -1, -1, -1, -1, -1, -1, 128, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, -1, -1, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, -1, -1, -1, -1, -1, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, -1, -1, -1, -1, -1, -1, 62, 63, 64, 65, 66, 67, -1, 69, 70, -1, -1, 73, -1, -1, -1, 77, 78, 79, 80, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 91, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, -1, -1, 118, -1, -1, -1, -1, -1, -1, -1, -1, 127, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, -1, -1, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, -1, -1, -1, -1, -1, 44, 45, 46, 47, 48, 49, 50, 51, 52, -1, 54, -1, -1, -1, -1, -1, -1, -1, 62, 63, 64, 65, 66, 67, -1, 69, 70, -1, -1, 73, -1, -1, -1, 77, 78, 79, 80, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 91, -1, 93, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, -1, -1, 118, -1, -1, -1, -1, -1, -1, -1, -1, 127, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, -1, -1, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, -1, -1, -1, -1, -1, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, 54, -1, -1, -1, -1, -1, -1, -1, 62, 63, 64, 65, 66, 67, -1, 69, 70, -1, -1, 73, -1, -1, -1, 77, 78, 79, 80, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 91, -1, 93, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, -1, -1, 118, -1, -1, -1, -1, -1, -1, -1, -1, 127, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, -1, -1, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, -1, -1, -1, -1, -1, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, 54, -1, -1, -1, -1, -1, -1, -1, 62, 63, 64, 65, 66, 67, -1, 69, 70, -1, -1, 73, -1, -1, -1, 77, 78, 79, 80, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 91, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, -1, -1, 118, -1, 3, 4, 5, -1, 7, -1, -1, 127, 11, 12, -1, -1, -1, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, 92, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 3, 4, 5, 6, 7, 116, 117, 118, 11, 12, -1, -1, -1, 16, 125, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, 38, -1, -1, -1, -1, -1, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, -1, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, 116, 117, 118, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, 38, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, 92, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, 116, 117, 118, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, 92, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, 116, 117, 118, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, 92, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, 116, 117, 118, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, 92, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, 116, 117, 118, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, 92, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, 116, 117, 118, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, 92, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, 116, 117, 118, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, 92, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, 116, 117, 118, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, 92, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, 116, 117, 118, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, 92, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, 116, 117, 118, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, 92, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, 116, 117, 118, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, 92, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, 116, 117, 118, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, 92, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, 116, 117, 118, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, 38, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, -1, -1, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, 116, 117, 118, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, -1, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, 116, 117, 118, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, -1, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, 116, 117, 118, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, -1, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, 116, 117, 118, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, -1, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, 116, 117, 118, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, -1, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, 116, 117, 118, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, -1, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, 116, 117, 118, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, -1, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, 116, 117, 118, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, -1, -1, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, 116, 117, 118, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, 62, 63, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, -1, -1, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, 116, 117, 118, 16, -1, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, -1, 93, 94, 95, 96, 97, 98, -1, -1, -1, 3, 4, 5, -1, 7, -1, 108, -1, 11, 12, -1, -1, -1, 16, 116, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 79, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, -1, -1, 93, 94, 95, 96, 97, 98, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, -1, -1, -1, 16, 116, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, -1, 93, 94, 95, 96, 97, 98, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, -1, -1, -1, 16, 116, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, 91, -1, 93, 94, 95, 96, 97, 98, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, -1, -1, -1, 16, 116, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, -1, -1, 93, 94, 95, 96, 97, 98, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, -1, -1, -1, 16, 116, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, -1, -1, 93, 94, 95, 96, 97, 98, -1, -1, -1, 3, 4, 5, -1, 7, -1, -1, -1, 11, 12, -1, -1, -1, 16, 116, 18, 19, 20, 21, 22, 23, 24, -1, -1, -1, -1, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45, -1, -1, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, -1, 59, 60, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 82, -1, -1, 85, 86, -1, 88, 89, -1, -1, -1, 93, 94, 95, 96, 97, 98, -1, -1, -1, -1, -1, -1, 50, 51, -1, -1, 54, -1, -1, -1, -1, -1, -1, 116, 62, 63, 64, 65, 66, 67, -1, 69, 70, -1, -1, 73, -1, -1, -1, 77, 78, 79, 80, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 91, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 50, 51, 118, -1, 54, -1, -1, -1, -1, -1, -1, 127, 62, 63, 64, 65, 66, 67, -1, 69, 70, -1, -1, 73, -1, -1, -1, 77, 78, 79, 80, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 91, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 50, 51, 118, -1, 54, -1, -1, -1, -1, -1, -1, 127, 62, 63, 64, 65, 66, 67, -1, 69, 70, -1, -1, 73, -1, -1, -1, 77, 78, 79, 80, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 91, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 50, 51, 118, -1, 54, -1, -1, -1, -1, -1, -1, 127, 62, 63, 64, 65, 66, 67, -1, 69, 70, -1, -1, 73, -1, -1, -1, 77, 78, 79, 80, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 91, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 50, 51, 118, -1, 54, -1, -1, -1, -1, -1, -1, 127, 62, 63, 64, 65, 66, 67, -1, 69, 70, -1, -1, 73, -1, -1, -1, 77, 78, 79, 80, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 91, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 50, 51, 118, -1, 54, -1, -1, -1, -1, -1, -1, 127, 62, 63, 64, 65, 66, 67, -1, 69, 70, -1, -1, 73, -1, -1, -1, 77, 78, 79, 80, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 91, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 50, 51, 118, -1, 54, -1, -1, -1, -1, -1, -1, 127, 62, 63, 64, 65, 66, 67, -1, 69, 70, -1, -1, 73, -1, -1, -1, 77, 78, 79, 80, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 91, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 50, 51, 118, -1, 54, -1, -1, -1, -1, -1, -1, 127, 62, 63, 64, 65, 66, 67, -1, 69, 70, -1, -1, 73, -1, -1, -1, 77, 78, 79, 80, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 91, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 50, 51, 118, -1, 54, -1, -1, -1, -1, -1, -1, 127, 62, 63, 64, 65, 66, 67, -1, 69, 70, -1, -1, 73, -1, -1, -1, 77, 78, 79, 80, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 91, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 50, 51, 118, -1, 54, -1, -1, -1, -1, -1, -1, 127, 62, 63, 64, 65, 66, 67, -1, 69, 70, -1, -1, 73, -1, -1, -1, 77, 78, 79, 80, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 91, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 50, 51, 118, -1, 54, -1, -1, -1, -1, -1, -1, 127, 62, 63, 64, 65, 66, 67, -1, 69, 70, -1, -1, 73, -1, -1, -1, 77, 78, 79, 80, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 91, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, -1, -1, 118, -1, -1, -1, -1, -1, -1, -1, -1, 127, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, -1, -1, 79, 80, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 104, -1, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, -1, 130, 79, 80, -1, -1, -1, 84, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, -1, -1, 79, 80, -1, -1, 104, -1, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, -1, -1, -1, -1, -1, -1, -1, -1, -1, 104, 126, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 126, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, -1, -1, 79, 80, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, -1, -1, 79, 80, -1, -1, -1, -1, -1, -1, 104, -1, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, -1, -1, -1, -1, -1, -1, -1, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 64, 65, 66, 67, 68, 69, 70, -1, -1, 73, 74, -1, -1, -1, -1, 79, 80, 64, 65, 66, 67, 68, 69, 70, -1, -1, 73, 74, -1, -1, -1, -1, 79, 80, -1, -1, -1, -1, -1, -1, -1, -1, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, -1, -1, -1, -1, -1, -1, -1, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115 }; /* YYSTOS[STATE-NUM] -- The (internal number of the) accessing symbol of state STATE-NUM. */ static const yytype_uint16 yystos[] = { 0, 133, 134, 0, 1, 3, 4, 5, 6, 7, 11, 12, 16, 18, 19, 20, 21, 22, 23, 24, 29, 30, 31, 32, 33, 34, 35, 38, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 59, 60, 62, 63, 82, 85, 86, 88, 89, 91, 93, 94, 95, 96, 97, 98, 116, 117, 118, 136, 137, 138, 141, 143, 144, 148, 149, 151, 152, 153, 154, 155, 165, 182, 199, 209, 210, 223, 224, 225, 226, 227, 228, 229, 232, 240, 242, 243, 244, 245, 246, 247, 266, 275, 138, 21, 22, 29, 30, 31, 45, 50, 54, 79, 82, 85, 116, 156, 157, 182, 199, 244, 247, 266, 157, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 44, 45, 46, 47, 48, 49, 50, 51, 54, 62, 63, 64, 65, 66, 67, 69, 70, 73, 77, 78, 79, 80, 91, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 118, 127, 128, 158, 163, 164, 245, 261, 32, 33, 34, 35, 48, 49, 50, 54, 158, 159, 160, 161, 240, 242, 183, 82, 141, 142, 155, 199, 244, 246, 247, 142, 130, 131, 142, 270, 273, 274, 186, 188, 82, 149, 155, 199, 204, 244, 247, 91, 92, 117, 148, 165, 167, 171, 178, 180, 264, 265, 171, 171, 128, 173, 174, 128, 169, 173, 141, 52, 160, 130, 271, 140, 120, 165, 199, 165, 54, 85, 136, 150, 151, 141, 91, 148, 168, 180, 264, 275, 180, 263, 264, 275, 82, 154, 199, 244, 247, 52, 53, 55, 158, 235, 241, 234, 235, 235, 129, 230, 129, 233, 56, 57, 143, 165, 165, 270, 274, 39, 40, 41, 42, 43, 36, 37, 28, 206, 103, 126, 85, 91, 152, 103, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 79, 80, 104, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 81, 122, 124, 81, 124, 26, 120, 211, 226, 83, 83, 169, 173, 211, 271, 141, 50, 54, 156, 56, 57, 1, 107, 248, 273, 81, 122, 124, 195, 262, 196, 81, 124, 269, 126, 135, 136, 54, 13, 105, 200, 273, 103, 81, 122, 124, 83, 83, 200, 270, 15, 17, 216, 131, 142, 142, 54, 81, 122, 124, 25, 167, 167, 84, 126, 179, 275, 126, 179, 125, 171, 86, 171, 175, 148, 171, 180, 209, 275, 52, 59, 60, 139, 128, 166, 120, 136, 81, 124, 83, 150, 125, 125, 184, 165, 271, 123, 126, 130, 272, 126, 272, 126, 272, 121, 272, 54, 81, 122, 124, 58, 99, 100, 101, 236, 101, 236, 101, 61, 101, 101, 231, 236, 101, 58, 101, 64, 64, 138, 142, 142, 142, 142, 138, 141, 141, 207, 91, 143, 167, 180, 181, 150, 154, 126, 143, 165, 167, 181, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 165, 50, 51, 54, 163, 267, 268, 168, 50, 51, 54, 163, 267, 50, 54, 267, 267, 214, 212, 143, 165, 143, 165, 90, 145, 193, 273, 249, 192, 50, 54, 156, 267, 168, 267, 135, 141, 50, 52, 53, 54, 55, 91, 92, 110, 113, 128, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 197, 162, 10, 8, 219, 275, 136, 13, 165, 50, 54, 168, 50, 54, 136, 216, 136, 91, 180, 217, 10, 27, 105, 201, 273, 201, 50, 54, 168, 50, 54, 190, 126, 179, 167, 91, 167, 178, 264, 91, 167, 265, 125, 91, 165, 167, 172, 176, 178, 264, 271, 126, 81, 124, 271, 125, 160, 185, 165, 136, 121, 165, 125, 271, 271, 91, 167, 50, 54, 168, 50, 54, 238, 237, 129, 236, 129, 165, 165, 72, 108, 205, 275, 167, 126, 125, 43, 105, 83, 83, 169, 173, 123, 83, 83, 169, 170, 173, 275, 170, 173, 170, 173, 205, 205, 146, 273, 142, 135, 123, 10, 271, 103, 251, 135, 273, 126, 260, 275, 126, 260, 50, 126, 260, 50, 158, 160, 167, 181, 220, 275, 15, 203, 275, 14, 202, 203, 83, 123, 83, 83, 203, 10, 10, 167, 126, 200, 187, 189, 123, 142, 167, 126, 179, 167, 167, 126, 177, 125, 126, 179, 125, 148, 209, 267, 267, 125, 141, 121, 125, 165, 123, 136, 52, 53, 55, 239, 247, 108, 204, 208, 91, 167, 165, 165, 143, 165, 165, 145, 83, 143, 165, 143, 165, 145, 215, 213, 205, 194, 273, 10, 125, 167, 271, 10, 252, 255, 257, 259, 50, 254, 257, 198, 84, 221, 275, 136, 9, 222, 275, 142, 10, 83, 10, 91, 136, 136, 136, 201, 179, 91, 179, 179, 91, 178, 180, 264, 125, 91, 271, 125, 271, 121, 108, 136, 167, 143, 165, 136, 136, 147, 135, 125, 126, 260, 260, 260, 250, 82, 155, 199, 244, 247, 200, 136, 200, 167, 203, 216, 218, 10, 10, 191, 165, 167, 126, 179, 126, 179, 167, 125, 10, 10, 121, 136, 10, 257, 135, 54, 81, 122, 124, 136, 136, 136, 179, 179, 91, 264, 91, 179, 121, 260, 10, 50, 54, 168, 50, 54, 219, 202, 10, 167, 126, 179, 167, 123, 179, 91, 179, 167, 179 }; #define yyerrok (yyerrstatus = 0) #define yyclearin (yychar = YYEMPTY) #define YYEMPTY (-2) #define YYEOF 0 #define YYACCEPT goto yyacceptlab #define YYABORT goto yyabortlab #define YYERROR goto yyerrorlab /* Like YYERROR except do call yyerror. This remains here temporarily to ease the transition to the new meaning of YYERROR, for GCC. Once GCC version 2 has supplanted version 1, this can go. */ #define YYFAIL goto yyerrlab #define YYRECOVERING() (!!yyerrstatus) #define YYBACKUP(Token, Value) \ do \ if (yychar == YYEMPTY && yylen == 1) \ { \ yychar = (Token); \ yylval = (Value); \ yytoken = YYTRANSLATE (yychar); \ YYPOPSTACK (1); \ goto yybackup; \ } \ else \ { \ yyerror (YY_("syntax error: cannot back up")); \ YYERROR; \ } \ while (YYID (0)) #define YYTERROR 1 #define YYERRCODE 256 /* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N]. If N is 0, then set CURRENT to the empty location which ends the previous symbol: RHS[0] (always defined). */ #define YYRHSLOC(Rhs, K) ((Rhs)[K]) #ifndef YYLLOC_DEFAULT # define YYLLOC_DEFAULT(Current, Rhs, N) \ do \ if (YYID (N)) \ { \ (Current).first_line = YYRHSLOC (Rhs, 1).first_line; \ (Current).first_column = YYRHSLOC (Rhs, 1).first_column; \ (Current).last_line = YYRHSLOC (Rhs, N).last_line; \ (Current).last_column = YYRHSLOC (Rhs, N).last_column; \ } \ else \ { \ (Current).first_line = (Current).last_line = \ YYRHSLOC (Rhs, 0).last_line; \ (Current).first_column = (Current).last_column = \ YYRHSLOC (Rhs, 0).last_column; \ } \ while (YYID (0)) #endif /* YY_LOCATION_PRINT -- Print the location on the stream. This macro was not mandated originally: define only if we know we won't break user code: when these are the locations we know. */ #ifndef YY_LOCATION_PRINT # if YYLTYPE_IS_TRIVIAL # define YY_LOCATION_PRINT(File, Loc) \ fprintf (File, "%d.%d-%d.%d", \ (Loc).first_line, (Loc).first_column, \ (Loc).last_line, (Loc).last_column) # else # define YY_LOCATION_PRINT(File, Loc) ((void) 0) # endif #endif /* YYLEX -- calling `yylex' with the right arguments. */ #ifdef YYLEX_PARAM # define YYLEX yylex (YYLEX_PARAM) #else # define YYLEX yylex () #endif /* Enable debugging if requested. */ #if YYDEBUG # ifndef YYFPRINTF # include /* INFRINGES ON USER NAME SPACE */ # define YYFPRINTF fprintf # endif # define YYDPRINTF(Args) \ do { \ if (yydebug) \ YYFPRINTF Args; \ } while (YYID (0)) # define YY_SYMBOL_PRINT(Title, Type, Value, Location) \ do { \ if (yydebug) \ { \ YYFPRINTF (stderr, "%s ", Title); \ yy_symbol_print (stderr, \ Type, Value); \ YYFPRINTF (stderr, "\n"); \ } \ } while (YYID (0)) /*--------------------------------. | Print this symbol on YYOUTPUT. | `--------------------------------*/ /*ARGSUSED*/ #if (defined __STDC__ || defined __C99__FUNC__ \ || defined __cplusplus || defined _MSC_VER) static void yy_symbol_value_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep) #else static void yy_symbol_value_print (yyoutput, yytype, yyvaluep) FILE *yyoutput; int yytype; YYSTYPE const * const yyvaluep; #endif { if (!yyvaluep) return; # ifdef YYPRINT if (yytype < YYNTOKENS) YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep); # else YYUSE (yyoutput); # endif switch (yytype) { default: break; } } /*--------------------------------. | Print this symbol on YYOUTPUT. | `--------------------------------*/ #if (defined __STDC__ || defined __C99__FUNC__ \ || defined __cplusplus || defined _MSC_VER) static void yy_symbol_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep) #else static void yy_symbol_print (yyoutput, yytype, yyvaluep) FILE *yyoutput; int yytype; YYSTYPE const * const yyvaluep; #endif { if (yytype < YYNTOKENS) YYFPRINTF (yyoutput, "token %s (", yytname[yytype]); else YYFPRINTF (yyoutput, "nterm %s (", yytname[yytype]); yy_symbol_value_print (yyoutput, yytype, yyvaluep); YYFPRINTF (yyoutput, ")"); } /*------------------------------------------------------------------. | yy_stack_print -- Print the state stack from its BOTTOM up to its | | TOP (included). | `------------------------------------------------------------------*/ #if (defined __STDC__ || defined __C99__FUNC__ \ || defined __cplusplus || defined _MSC_VER) static void yy_stack_print (yytype_int16 *bottom, yytype_int16 *top) #else static void yy_stack_print (bottom, top) yytype_int16 *bottom; yytype_int16 *top; #endif { YYFPRINTF (stderr, "Stack now"); for (; bottom <= top; ++bottom) YYFPRINTF (stderr, " %d", *bottom); YYFPRINTF (stderr, "\n"); } # define YY_STACK_PRINT(Bottom, Top) \ do { \ if (yydebug) \ yy_stack_print ((Bottom), (Top)); \ } while (YYID (0)) /*------------------------------------------------. | Report that the YYRULE is going to be reduced. | `------------------------------------------------*/ #if (defined __STDC__ || defined __C99__FUNC__ \ || defined __cplusplus || defined _MSC_VER) static void yy_reduce_print (YYSTYPE *yyvsp, int yyrule) #else static void yy_reduce_print (yyvsp, yyrule) YYSTYPE *yyvsp; int yyrule; #endif { int yynrhs = yyr2[yyrule]; int yyi; unsigned long int yylno = yyrline[yyrule]; YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n", yyrule - 1, yylno); /* The symbols being reduced. */ for (yyi = 0; yyi < yynrhs; yyi++) { fprintf (stderr, " $%d = ", yyi + 1); yy_symbol_print (stderr, yyrhs[yyprhs[yyrule] + yyi], &(yyvsp[(yyi + 1) - (yynrhs)]) ); fprintf (stderr, "\n"); } } # define YY_REDUCE_PRINT(Rule) \ do { \ if (yydebug) \ yy_reduce_print (yyvsp, Rule); \ } while (YYID (0)) /* Nonzero means print parse trace. It is left uninitialized so that multiple parsers can coexist. */ int yydebug; #else /* !YYDEBUG */ # define YYDPRINTF(Args) # define YY_SYMBOL_PRINT(Title, Type, Value, Location) # define YY_STACK_PRINT(Bottom, Top) # define YY_REDUCE_PRINT(Rule) #endif /* !YYDEBUG */ /* YYINITDEPTH -- initial size of the parser's stacks. */ #ifndef YYINITDEPTH # define YYINITDEPTH 200 #endif /* YYMAXDEPTH -- maximum size the stacks can grow to (effective only if the built-in stack extension method is used). Do not make this value too large; the results are undefined if YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH) evaluated with infinite-precision integer arithmetic. */ #ifndef YYMAXDEPTH # define YYMAXDEPTH 10000 #endif #if YYERROR_VERBOSE # ifndef yystrlen # if defined __GLIBC__ && defined _STRING_H # define yystrlen strlen # else /* Return the length of YYSTR. */ #if (defined __STDC__ || defined __C99__FUNC__ \ || defined __cplusplus || defined _MSC_VER) static YYSIZE_T yystrlen (const char *yystr) #else static YYSIZE_T yystrlen (yystr) const char *yystr; #endif { YYSIZE_T yylen; for (yylen = 0; yystr[yylen]; yylen++) continue; return yylen; } # endif # endif # ifndef yystpcpy # if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE # define yystpcpy stpcpy # else /* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in YYDEST. */ #if (defined __STDC__ || defined __C99__FUNC__ \ || defined __cplusplus || defined _MSC_VER) static char * yystpcpy (char *yydest, const char *yysrc) #else static char * yystpcpy (yydest, yysrc) char *yydest; const char *yysrc; #endif { char *yyd = yydest; const char *yys = yysrc; while ((*yyd++ = *yys++) != '\0') continue; return yyd - 1; } # endif # endif # ifndef yytnamerr /* Copy to YYRES the contents of YYSTR after stripping away unnecessary quotes and backslashes, so that it's suitable for yyerror. The heuristic is that double-quoting is unnecessary unless the string contains an apostrophe, a comma, or backslash (other than backslash-backslash). YYSTR is taken from yytname. If YYRES is null, do not copy; instead, return the length of what the result would have been. */ static YYSIZE_T yytnamerr (char *yyres, const char *yystr) { if (*yystr == '"') { YYSIZE_T yyn = 0; char const *yyp = yystr; for (;;) switch (*++yyp) { case '\'': case ',': goto do_not_strip_quotes; case '\\': if (*++yyp != '\\') goto do_not_strip_quotes; /* Fall through. */ default: if (yyres) yyres[yyn] = *yyp; yyn++; break; case '"': if (yyres) yyres[yyn] = '\0'; return yyn; } do_not_strip_quotes: ; } if (! yyres) return yystrlen (yystr); return yystpcpy (yyres, yystr) - yyres; } # endif /* Copy into YYRESULT an error message about the unexpected token YYCHAR while in state YYSTATE. Return the number of bytes copied, including the terminating null byte. If YYRESULT is null, do not copy anything; just return the number of bytes that would be copied. As a special case, return 0 if an ordinary "syntax error" message will do. Return YYSIZE_MAXIMUM if overflow occurs during size calculation. */ static YYSIZE_T yysyntax_error (char *yyresult, int yystate, int yychar) { int yyn = yypact[yystate]; if (! (YYPACT_NINF < yyn && yyn <= YYLAST)) return 0; else { int yytype = YYTRANSLATE (yychar); YYSIZE_T yysize0 = yytnamerr (0, yytname[yytype]); YYSIZE_T yysize = yysize0; YYSIZE_T yysize1; int yysize_overflow = 0; enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 }; char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM]; int yyx; # if 0 /* This is so xgettext sees the translatable formats that are constructed on the fly. */ YY_("syntax error, unexpected %s"); YY_("syntax error, unexpected %s, expecting %s"); YY_("syntax error, unexpected %s, expecting %s or %s"); YY_("syntax error, unexpected %s, expecting %s or %s or %s"); YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s"); # endif char *yyfmt; char const *yyf; static char const yyunexpected[] = "syntax error, unexpected %s"; static char const yyexpecting[] = ", expecting %s"; static char const yyor[] = " or %s"; char yyformat[sizeof yyunexpected + sizeof yyexpecting - 1 + ((YYERROR_VERBOSE_ARGS_MAXIMUM - 2) * (sizeof yyor - 1))]; char const *yyprefix = yyexpecting; /* Start YYX at -YYN if negative to avoid negative indexes in YYCHECK. */ int yyxbegin = yyn < 0 ? -yyn : 0; /* Stay within bounds of both yycheck and yytname. */ int yychecklim = YYLAST - yyn + 1; int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; int yycount = 1; yyarg[0] = yytname[yytype]; yyfmt = yystpcpy (yyformat, yyunexpected); for (yyx = yyxbegin; yyx < yyxend; ++yyx) if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR) { if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM) { yycount = 1; yysize = yysize0; yyformat[sizeof yyunexpected - 1] = '\0'; break; } yyarg[yycount++] = yytname[yyx]; yysize1 = yysize + yytnamerr (0, yytname[yyx]); yysize_overflow |= (yysize1 < yysize); yysize = yysize1; yyfmt = yystpcpy (yyfmt, yyprefix); yyprefix = yyor; } yyf = YY_(yyformat); yysize1 = yysize + yystrlen (yyf); yysize_overflow |= (yysize1 < yysize); yysize = yysize1; if (yysize_overflow) return YYSIZE_MAXIMUM; if (yyresult) { /* Avoid sprintf, as that infringes on the user's name space. Don't have undefined behavior even if the translation produced a string with the wrong number of "%s"s. */ char *yyp = yyresult; int yyi = 0; while ((*yyp = *yyf) != '\0') { if (*yyp == '%' && yyf[1] == 's' && yyi < yycount) { yyp += yytnamerr (yyp, yyarg[yyi++]); yyf += 2; } else { yyp++; yyf++; } } } return yysize; } } #endif /* YYERROR_VERBOSE */ /*-----------------------------------------------. | Release the memory associated to this symbol. | `-----------------------------------------------*/ /*ARGSUSED*/ #if (defined __STDC__ || defined __C99__FUNC__ \ || defined __cplusplus || defined _MSC_VER) static void yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep) #else static void yydestruct (yymsg, yytype, yyvaluep) const char *yymsg; int yytype; YYSTYPE *yyvaluep; #endif { YYUSE (yyvaluep); if (!yymsg) yymsg = "Deleting"; YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp); switch (yytype) { default: break; } } /* Prevent warnings from -Wmissing-prototypes. */ #ifdef YYPARSE_PARAM #if defined __STDC__ || defined __cplusplus int yyparse (void *YYPARSE_PARAM); #else int yyparse (); #endif #else /* ! YYPARSE_PARAM */ #if defined __STDC__ || defined __cplusplus int yyparse (void); #else int yyparse (); #endif #endif /* ! YYPARSE_PARAM */ /* The look-ahead symbol. */ int yychar; /* The semantic value of the look-ahead symbol. */ YYSTYPE yylval; /* Number of syntax errors so far. */ int yynerrs; /*----------. | yyparse. | `----------*/ #ifdef YYPARSE_PARAM #if (defined __STDC__ || defined __C99__FUNC__ \ || defined __cplusplus || defined _MSC_VER) int yyparse (void *YYPARSE_PARAM) #else int yyparse (YYPARSE_PARAM) void *YYPARSE_PARAM; #endif #else /* ! YYPARSE_PARAM */ #if (defined __STDC__ || defined __C99__FUNC__ \ || defined __cplusplus || defined _MSC_VER) int yyparse (void) #else int yyparse () #endif #endif { int yystate; int yyn; int yyresult; /* Number of tokens to shift before error messages enabled. */ int yyerrstatus; /* Look-ahead token as an internal (translated) token number. */ int yytoken = 0; #if YYERROR_VERBOSE /* Buffer for error messages, and its allocated size. */ char yymsgbuf[128]; char *yymsg = yymsgbuf; YYSIZE_T yymsg_alloc = sizeof yymsgbuf; #endif /* Three stacks and their tools: `yyss': related to states, `yyvs': related to semantic values, `yyls': related to locations. Refer to the stacks thru separate pointers, to allow yyoverflow to reallocate them elsewhere. */ /* The state stack. */ yytype_int16 yyssa[YYINITDEPTH]; yytype_int16 *yyss = yyssa; yytype_int16 *yyssp; /* The semantic value stack. */ YYSTYPE yyvsa[YYINITDEPTH]; YYSTYPE *yyvs = yyvsa; YYSTYPE *yyvsp; #define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N)) YYSIZE_T yystacksize = YYINITDEPTH; /* The variables used to return semantic value and location from the action routines. */ YYSTYPE yyval; /* The number of symbols on the RHS of the reduced rule. Keep to zero when no symbol should be popped. */ int yylen = 0; YYDPRINTF ((stderr, "Starting parse\n")); yystate = 0; yyerrstatus = 0; yynerrs = 0; yychar = YYEMPTY; /* Cause a token to be read. */ /* Initialize stack pointers. Waste one element of value and location stack so that they stay on the same level as the state stack. The wasted elements are never initialized. */ yyssp = yyss; yyvsp = yyvs; goto yysetstate; /*------------------------------------------------------------. | yynewstate -- Push a new state, which is found in yystate. | `------------------------------------------------------------*/ yynewstate: /* In all cases, when you get here, the value and location stacks have just been pushed. So pushing a state here evens the stacks. */ yyssp++; yysetstate: *yyssp = yystate; if (yyss + yystacksize - 1 <= yyssp) { /* Get the current used size of the three stacks, in elements. */ YYSIZE_T yysize = yyssp - yyss + 1; #ifdef yyoverflow { /* Give user a chance to reallocate the stack. Use copies of these so that the &'s don't force the real ones into memory. */ YYSTYPE *yyvs1 = yyvs; yytype_int16 *yyss1 = yyss; /* Each stack pointer address is followed by the size of the data in use in that stack, in bytes. This used to be a conditional around just the two extra args, but that might be undefined if yyoverflow is a macro. */ yyoverflow (YY_("memory exhausted"), &yyss1, yysize * sizeof (*yyssp), &yyvs1, yysize * sizeof (*yyvsp), &yystacksize); yyss = yyss1; yyvs = yyvs1; } #else /* no yyoverflow */ # ifndef YYSTACK_RELOCATE goto yyexhaustedlab; # else /* Extend the stack our own way. */ if (YYMAXDEPTH <= yystacksize) goto yyexhaustedlab; yystacksize *= 2; if (YYMAXDEPTH < yystacksize) yystacksize = YYMAXDEPTH; { yytype_int16 *yyss1 = yyss; union yyalloc *yyptr = (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize)); if (! yyptr) goto yyexhaustedlab; YYSTACK_RELOCATE (yyss); YYSTACK_RELOCATE (yyvs); # undef YYSTACK_RELOCATE if (yyss1 != yyssa) YYSTACK_FREE (yyss1); } # endif #endif /* no yyoverflow */ yyssp = yyss + yysize - 1; yyvsp = yyvs + yysize - 1; YYDPRINTF ((stderr, "Stack size increased to %lu\n", (unsigned long int) yystacksize)); if (yyss + yystacksize - 1 <= yyssp) YYABORT; } YYDPRINTF ((stderr, "Entering state %d\n", yystate)); goto yybackup; /*-----------. | yybackup. | `-----------*/ yybackup: /* Do appropriate processing given the current state. Read a look-ahead token if we need one and don't already have one. */ /* First try to decide what to do without reference to look-ahead token. */ yyn = yypact[yystate]; if (yyn == YYPACT_NINF) goto yydefault; /* Not known => get a look-ahead token if don't already have one. */ /* YYCHAR is either YYEMPTY or YYEOF or a valid look-ahead symbol. */ if (yychar == YYEMPTY) { YYDPRINTF ((stderr, "Reading a token: ")); yychar = YYLEX; } if (yychar <= YYEOF) { yychar = yytoken = YYEOF; YYDPRINTF ((stderr, "Now at end of input.\n")); } else { yytoken = YYTRANSLATE (yychar); YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); } /* If the proper action on seeing token YYTOKEN is to reduce or to detect an error, take that action. */ yyn += yytoken; if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) goto yydefault; yyn = yytable[yyn]; if (yyn <= 0) { if (yyn == 0 || yyn == YYTABLE_NINF) goto yyerrlab; yyn = -yyn; goto yyreduce; } if (yyn == YYFINAL) YYACCEPT; /* Count tokens shifted since error; after three, turn off error status. */ if (yyerrstatus) yyerrstatus--; /* Shift the look-ahead token. */ YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); /* Discard the shifted token unless it is eof. */ if (yychar != YYEOF) yychar = YYEMPTY; yystate = yyn; *++yyvsp = yylval; goto yynewstate; /*-----------------------------------------------------------. | yydefault -- do the default action for the current state. | `-----------------------------------------------------------*/ yydefault: yyn = yydefact[yystate]; if (yyn == 0) goto yyerrlab; goto yyreduce; /*-----------------------------. | yyreduce -- Do a reduction. | `-----------------------------*/ yyreduce: /* yyn is the number of a rule to reduce with. */ yylen = yyr2[yyn]; /* If YYLEN is nonzero, implement the default value of the action: `$$ = $1'. Otherwise, the following line sets YYVAL to garbage. This behavior is undocumented and Bison users should not rely upon it. Assigning to YYVAL unconditionally makes the parser a bit smaller, and it avoids a GCC warning that YYVAL may be used uninitialized. */ yyval = yyvsp[1-yylen]; YY_REDUCE_PRINT (yyn); switch (yyn) { case 2: #line 350 "parse.y" { lex_state = EXPR_BEG; top_local_init(); if (ruby_class == rb_cObject) class_nest = 0; else class_nest = 1; } break; case 3: #line 357 "parse.y" { if ((yyvsp[(2) - (2)].node) && !compile_for_eval) { /* last expression should not be void */ if (nd_type((yyvsp[(2) - (2)].node)) != NODE_BLOCK) void_expr((yyvsp[(2) - (2)].node)); else { NODE *node = (yyvsp[(2) - (2)].node); while (node->nd_next) { node = node->nd_next; } void_expr(node->nd_head); } } ruby_eval_tree = block_append(ruby_eval_tree, (yyvsp[(2) - (2)].node)); top_local_setup(); class_nest = 0; } break; case 4: #line 379 "parse.y" { (yyval.node) = (yyvsp[(1) - (4)].node); if ((yyvsp[(2) - (4)].node)) { (yyval.node) = NEW_RESCUE((yyvsp[(1) - (4)].node), (yyvsp[(2) - (4)].node), (yyvsp[(3) - (4)].node)); } else if ((yyvsp[(3) - (4)].node)) { rb_warn("else without rescue is useless"); (yyval.node) = block_append((yyval.node), (yyvsp[(3) - (4)].node)); } if ((yyvsp[(4) - (4)].node)) { (yyval.node) = NEW_ENSURE((yyval.node), (yyvsp[(4) - (4)].node)); } fixpos((yyval.node), (yyvsp[(1) - (4)].node)); } break; case 5: #line 396 "parse.y" { void_stmts((yyvsp[(1) - (2)].node)); fixup_nodes(&deferred_nodes); (yyval.node) = (yyvsp[(1) - (2)].node); } break; case 7: #line 405 "parse.y" { (yyval.node) = newline_node((yyvsp[(1) - (1)].node)); } break; case 8: #line 409 "parse.y" { (yyval.node) = block_append((yyvsp[(1) - (3)].node), newline_node((yyvsp[(3) - (3)].node))); } break; case 9: #line 413 "parse.y" { (yyval.node) = remove_begin((yyvsp[(2) - (2)].node)); } break; case 10: #line 418 "parse.y" {lex_state = EXPR_FNAME;} break; case 11: #line 419 "parse.y" { (yyval.node) = NEW_ALIAS((yyvsp[(2) - (4)].node), (yyvsp[(4) - (4)].node)); } break; case 12: #line 423 "parse.y" { (yyval.node) = NEW_VALIAS((yyvsp[(2) - (3)].id), (yyvsp[(3) - (3)].id)); } break; case 13: #line 427 "parse.y" { char buf[3]; sprintf(buf, "$%c", (char)(yyvsp[(3) - (3)].node)->nd_nth); (yyval.node) = NEW_VALIAS((yyvsp[(2) - (3)].id), rb_intern(buf)); } break; case 14: #line 434 "parse.y" { yyerror("can't make alias for the number variables"); (yyval.node) = 0; } break; case 15: #line 439 "parse.y" { (yyval.node) = (yyvsp[(2) - (2)].node); } break; case 16: #line 443 "parse.y" { (yyval.node) = NEW_IF(cond((yyvsp[(3) - (3)].node)), remove_begin((yyvsp[(1) - (3)].node)), 0); fixpos((yyval.node), (yyvsp[(3) - (3)].node)); if (cond_negative(&(yyval.node)->nd_cond)) { (yyval.node)->nd_else = (yyval.node)->nd_body; (yyval.node)->nd_body = 0; } } break; case 17: #line 452 "parse.y" { (yyval.node) = NEW_UNLESS(cond((yyvsp[(3) - (3)].node)), remove_begin((yyvsp[(1) - (3)].node)), 0); fixpos((yyval.node), (yyvsp[(3) - (3)].node)); if (cond_negative(&(yyval.node)->nd_cond)) { (yyval.node)->nd_body = (yyval.node)->nd_else; (yyval.node)->nd_else = 0; } } break; case 18: #line 461 "parse.y" { if ((yyvsp[(1) - (3)].node) && nd_type((yyvsp[(1) - (3)].node)) == NODE_BEGIN) { (yyval.node) = NEW_WHILE(cond((yyvsp[(3) - (3)].node)), (yyvsp[(1) - (3)].node)->nd_body, 0); } else { (yyval.node) = NEW_WHILE(cond((yyvsp[(3) - (3)].node)), (yyvsp[(1) - (3)].node), 1); } if (cond_negative(&(yyval.node)->nd_cond)) { nd_set_type((yyval.node), NODE_UNTIL); } } break; case 19: #line 473 "parse.y" { if ((yyvsp[(1) - (3)].node) && nd_type((yyvsp[(1) - (3)].node)) == NODE_BEGIN) { (yyval.node) = NEW_UNTIL(cond((yyvsp[(3) - (3)].node)), (yyvsp[(1) - (3)].node)->nd_body, 0); } else { (yyval.node) = NEW_UNTIL(cond((yyvsp[(3) - (3)].node)), (yyvsp[(1) - (3)].node), 1); } if (cond_negative(&(yyval.node)->nd_cond)) { nd_set_type((yyval.node), NODE_WHILE); } } break; case 20: #line 485 "parse.y" { NODE *resq = NEW_RESBODY(0, remove_begin((yyvsp[(3) - (3)].node)), 0); (yyval.node) = NEW_RESCUE(remove_begin((yyvsp[(1) - (3)].node)), resq, 0); } break; case 21: #line 490 "parse.y" { if (in_def || in_single) { yyerror("BEGIN in method"); } local_push(0); } break; case 22: #line 497 "parse.y" { ruby_eval_tree_begin = block_append(ruby_eval_tree_begin, NEW_PREEXE((yyvsp[(4) - (5)].node))); local_pop(); (yyval.node) = 0; } break; case 23: #line 504 "parse.y" { if (in_def || in_single) { rb_warn("END in method; use at_exit"); } (yyval.node) = NEW_ITER(0, NEW_POSTEXE(), (yyvsp[(3) - (4)].node)); } break; case 24: #line 512 "parse.y" { (yyval.node) = node_assign((yyvsp[(1) - (3)].node), (yyvsp[(3) - (3)].node)); } break; case 25: #line 516 "parse.y" { value_expr((yyvsp[(3) - (3)].node)); (yyvsp[(1) - (3)].node)->nd_value = ((yyvsp[(1) - (3)].node)->nd_head) ? NEW_TO_ARY((yyvsp[(3) - (3)].node)) : NEW_ARRAY((yyvsp[(3) - (3)].node)); (yyval.node) = (yyvsp[(1) - (3)].node); } break; case 26: #line 522 "parse.y" { value_expr((yyvsp[(3) - (3)].node)); if ((yyvsp[(1) - (3)].node)) { ID vid = (yyvsp[(1) - (3)].node)->nd_vid; if ((yyvsp[(2) - (3)].id) == tOROP) { (yyvsp[(1) - (3)].node)->nd_value = (yyvsp[(3) - (3)].node); (yyval.node) = NEW_OP_ASGN_OR(gettable(vid), (yyvsp[(1) - (3)].node)); if (is_asgn_or_id(vid)) { (yyval.node)->nd_aid = vid; } } else if ((yyvsp[(2) - (3)].id) == tANDOP) { (yyvsp[(1) - (3)].node)->nd_value = (yyvsp[(3) - (3)].node); (yyval.node) = NEW_OP_ASGN_AND(gettable(vid), (yyvsp[(1) - (3)].node)); } else { (yyval.node) = (yyvsp[(1) - (3)].node); (yyval.node)->nd_value = call_op(gettable(vid),(yyvsp[(2) - (3)].id),1,(yyvsp[(3) - (3)].node)); } } else { (yyval.node) = 0; } } break; case 27: #line 547 "parse.y" { NODE *args; value_expr((yyvsp[(6) - (6)].node)); if (!(yyvsp[(3) - (6)].node)) (yyvsp[(3) - (6)].node) = NEW_ZARRAY(); args = arg_concat((yyvsp[(6) - (6)].node), (yyvsp[(3) - (6)].node)); if ((yyvsp[(5) - (6)].id) == tOROP) { (yyvsp[(5) - (6)].id) = 0; } else if ((yyvsp[(5) - (6)].id) == tANDOP) { (yyvsp[(5) - (6)].id) = 1; } (yyval.node) = NEW_OP_ASGN1((yyvsp[(1) - (6)].node), (yyvsp[(5) - (6)].id), args); fixpos((yyval.node), (yyvsp[(1) - (6)].node)); } break; case 28: #line 563 "parse.y" { value_expr((yyvsp[(5) - (5)].node)); if ((yyvsp[(4) - (5)].id) == tOROP) { (yyvsp[(4) - (5)].id) = 0; } else if ((yyvsp[(4) - (5)].id) == tANDOP) { (yyvsp[(4) - (5)].id) = 1; } (yyval.node) = NEW_OP_ASGN2((yyvsp[(1) - (5)].node), (yyvsp[(3) - (5)].id), (yyvsp[(4) - (5)].id), (yyvsp[(5) - (5)].node)); fixpos((yyval.node), (yyvsp[(1) - (5)].node)); } break; case 29: #line 575 "parse.y" { value_expr((yyvsp[(5) - (5)].node)); if ((yyvsp[(4) - (5)].id) == tOROP) { (yyvsp[(4) - (5)].id) = 0; } else if ((yyvsp[(4) - (5)].id) == tANDOP) { (yyvsp[(4) - (5)].id) = 1; } (yyval.node) = NEW_OP_ASGN2((yyvsp[(1) - (5)].node), (yyvsp[(3) - (5)].id), (yyvsp[(4) - (5)].id), (yyvsp[(5) - (5)].node)); fixpos((yyval.node), (yyvsp[(1) - (5)].node)); } break; case 30: #line 587 "parse.y" { value_expr((yyvsp[(5) - (5)].node)); if ((yyvsp[(4) - (5)].id) == tOROP) { (yyvsp[(4) - (5)].id) = 0; } else if ((yyvsp[(4) - (5)].id) == tANDOP) { (yyvsp[(4) - (5)].id) = 1; } (yyval.node) = NEW_OP_ASGN2((yyvsp[(1) - (5)].node), (yyvsp[(3) - (5)].id), (yyvsp[(4) - (5)].id), (yyvsp[(5) - (5)].node)); fixpos((yyval.node), (yyvsp[(1) - (5)].node)); } break; case 31: #line 599 "parse.y" { rb_backref_error((yyvsp[(1) - (3)].node)); (yyval.node) = 0; } break; case 32: #line 604 "parse.y" { (yyval.node) = node_assign((yyvsp[(1) - (3)].node), NEW_SVALUE((yyvsp[(3) - (3)].node))); } break; case 33: #line 608 "parse.y" { (yyvsp[(1) - (3)].node)->nd_value = ((yyvsp[(1) - (3)].node)->nd_head) ? NEW_TO_ARY((yyvsp[(3) - (3)].node)) : NEW_ARRAY((yyvsp[(3) - (3)].node)); (yyval.node) = (yyvsp[(1) - (3)].node); } break; case 34: #line 613 "parse.y" { (yyvsp[(1) - (3)].node)->nd_value = (yyvsp[(3) - (3)].node); (yyval.node) = (yyvsp[(1) - (3)].node); } break; case 37: #line 622 "parse.y" { (yyval.node) = logop(NODE_AND, (yyvsp[(1) - (3)].node), (yyvsp[(3) - (3)].node)); } break; case 38: #line 626 "parse.y" { (yyval.node) = logop(NODE_OR, (yyvsp[(1) - (3)].node), (yyvsp[(3) - (3)].node)); } break; case 39: #line 630 "parse.y" { (yyval.node) = NEW_NOT(cond((yyvsp[(2) - (2)].node))); } break; case 40: #line 634 "parse.y" { (yyval.node) = NEW_NOT(cond((yyvsp[(2) - (2)].node))); } break; case 42: #line 641 "parse.y" { value_expr((yyval.node)); (yyval.node) = (yyvsp[(1) - (1)].node); } break; case 45: #line 650 "parse.y" { (yyval.node) = NEW_RETURN(ret_args((yyvsp[(2) - (2)].node))); } break; case 46: #line 654 "parse.y" { (yyval.node) = NEW_BREAK(ret_args((yyvsp[(2) - (2)].node))); } break; case 47: #line 658 "parse.y" { (yyval.node) = NEW_NEXT(ret_args((yyvsp[(2) - (2)].node))); } break; case 49: #line 665 "parse.y" { (yyval.node) = new_call((yyvsp[(1) - (4)].node), (yyvsp[(3) - (4)].id), (yyvsp[(4) - (4)].node)); } break; case 50: #line 669 "parse.y" { (yyval.node) = new_call((yyvsp[(1) - (4)].node), (yyvsp[(3) - (4)].id), (yyvsp[(4) - (4)].node)); } break; case 51: #line 675 "parse.y" { (yyval.vars) = dyna_push(); (yyvsp[(1) - (1)].num) = ruby_sourceline; } break; case 52: #line 679 "parse.y" {(yyval.vars) = ruby_dyna_vars;} break; case 53: #line 682 "parse.y" { (yyval.node) = NEW_ITER((yyvsp[(3) - (6)].node), 0, dyna_init((yyvsp[(5) - (6)].node), (yyvsp[(4) - (6)].vars))); nd_set_line((yyval.node), (yyvsp[(1) - (6)].num)); dyna_pop((yyvsp[(2) - (6)].vars)); } break; case 54: #line 690 "parse.y" { (yyval.node) = new_fcall((yyvsp[(1) - (2)].id), (yyvsp[(2) - (2)].node)); fixpos((yyval.node), (yyvsp[(2) - (2)].node)); } break; case 55: #line 695 "parse.y" { (yyval.node) = new_fcall((yyvsp[(1) - (3)].id), (yyvsp[(2) - (3)].node)); if ((yyvsp[(3) - (3)].node)) { if (nd_type((yyval.node)) == NODE_BLOCK_PASS) { rb_compile_error("both block arg and actual block given"); } (yyvsp[(3) - (3)].node)->nd_iter = (yyval.node); (yyval.node) = (yyvsp[(3) - (3)].node); } fixpos((yyval.node), (yyvsp[(2) - (3)].node)); } break; case 56: #line 707 "parse.y" { (yyval.node) = new_call((yyvsp[(1) - (4)].node), (yyvsp[(3) - (4)].id), (yyvsp[(4) - (4)].node)); fixpos((yyval.node), (yyvsp[(1) - (4)].node)); } break; case 57: #line 712 "parse.y" { (yyval.node) = new_call((yyvsp[(1) - (5)].node), (yyvsp[(3) - (5)].id), (yyvsp[(4) - (5)].node)); if ((yyvsp[(5) - (5)].node)) { if (nd_type((yyval.node)) == NODE_BLOCK_PASS) { rb_compile_error("both block arg and actual block given"); } (yyvsp[(5) - (5)].node)->nd_iter = (yyval.node); (yyval.node) = (yyvsp[(5) - (5)].node); } fixpos((yyval.node), (yyvsp[(1) - (5)].node)); } break; case 58: #line 724 "parse.y" { (yyval.node) = new_call((yyvsp[(1) - (4)].node), (yyvsp[(3) - (4)].id), (yyvsp[(4) - (4)].node)); fixpos((yyval.node), (yyvsp[(1) - (4)].node)); } break; case 59: #line 729 "parse.y" { (yyval.node) = new_call((yyvsp[(1) - (5)].node), (yyvsp[(3) - (5)].id), (yyvsp[(4) - (5)].node)); if ((yyvsp[(5) - (5)].node)) { if (nd_type((yyval.node)) == NODE_BLOCK_PASS) { rb_compile_error("both block arg and actual block given"); } (yyvsp[(5) - (5)].node)->nd_iter = (yyval.node); (yyval.node) = (yyvsp[(5) - (5)].node); } fixpos((yyval.node), (yyvsp[(1) - (5)].node)); } break; case 60: #line 741 "parse.y" { (yyval.node) = new_super((yyvsp[(2) - (2)].node)); fixpos((yyval.node), (yyvsp[(2) - (2)].node)); } break; case 61: #line 746 "parse.y" { (yyval.node) = new_yield((yyvsp[(2) - (2)].node)); fixpos((yyval.node), (yyvsp[(2) - (2)].node)); } break; case 63: #line 754 "parse.y" { (yyval.node) = (yyvsp[(2) - (3)].node); } break; case 65: #line 761 "parse.y" { (yyval.node) = NEW_MASGN(NEW_LIST((yyvsp[(2) - (3)].node)), 0); } break; case 66: #line 767 "parse.y" { (yyval.node) = NEW_MASGN((yyvsp[(1) - (1)].node), 0); } break; case 67: #line 771 "parse.y" { (yyval.node) = NEW_MASGN(list_append((yyvsp[(1) - (2)].node),(yyvsp[(2) - (2)].node)), 0); } break; case 68: #line 775 "parse.y" { (yyval.node) = NEW_MASGN((yyvsp[(1) - (3)].node), (yyvsp[(3) - (3)].node)); } break; case 69: #line 779 "parse.y" { (yyval.node) = NEW_MASGN((yyvsp[(1) - (2)].node), -1); } break; case 70: #line 783 "parse.y" { (yyval.node) = NEW_MASGN(0, (yyvsp[(2) - (2)].node)); } break; case 71: #line 787 "parse.y" { (yyval.node) = NEW_MASGN(0, -1); } break; case 73: #line 794 "parse.y" { (yyval.node) = (yyvsp[(2) - (3)].node); } break; case 74: #line 800 "parse.y" { (yyval.node) = NEW_LIST((yyvsp[(1) - (2)].node)); } break; case 75: #line 804 "parse.y" { (yyval.node) = list_append((yyvsp[(1) - (3)].node), (yyvsp[(2) - (3)].node)); } break; case 76: #line 810 "parse.y" { (yyval.node) = assignable((yyvsp[(1) - (1)].id), 0); } break; case 77: #line 814 "parse.y" { (yyval.node) = aryset((yyvsp[(1) - (4)].node), (yyvsp[(3) - (4)].node)); } break; case 78: #line 818 "parse.y" { (yyval.node) = attrset((yyvsp[(1) - (3)].node), (yyvsp[(3) - (3)].id)); } break; case 79: #line 822 "parse.y" { (yyval.node) = attrset((yyvsp[(1) - (3)].node), (yyvsp[(3) - (3)].id)); } break; case 80: #line 826 "parse.y" { (yyval.node) = attrset((yyvsp[(1) - (3)].node), (yyvsp[(3) - (3)].id)); } break; case 81: #line 830 "parse.y" { if (in_def || in_single) yyerror("dynamic constant assignment"); (yyval.node) = NEW_CDECL(0, 0, NEW_COLON2((yyvsp[(1) - (3)].node), (yyvsp[(3) - (3)].id))); } break; case 82: #line 836 "parse.y" { if (in_def || in_single) yyerror("dynamic constant assignment"); (yyval.node) = NEW_CDECL(0, 0, NEW_COLON3((yyvsp[(2) - (2)].id))); } break; case 83: #line 842 "parse.y" { rb_backref_error((yyvsp[(1) - (1)].node)); (yyval.node) = 0; } break; case 84: #line 849 "parse.y" { (yyval.node) = assignable((yyvsp[(1) - (1)].id), 0); } break; case 85: #line 853 "parse.y" { (yyval.node) = aryset((yyvsp[(1) - (4)].node), (yyvsp[(3) - (4)].node)); } break; case 86: #line 857 "parse.y" { (yyval.node) = attrset((yyvsp[(1) - (3)].node), (yyvsp[(3) - (3)].id)); } break; case 87: #line 861 "parse.y" { (yyval.node) = attrset((yyvsp[(1) - (3)].node), (yyvsp[(3) - (3)].id)); } break; case 88: #line 865 "parse.y" { (yyval.node) = attrset((yyvsp[(1) - (3)].node), (yyvsp[(3) - (3)].id)); } break; case 89: #line 869 "parse.y" { if (in_def || in_single) yyerror("dynamic constant assignment"); (yyval.node) = NEW_CDECL(0, 0, NEW_COLON2((yyvsp[(1) - (3)].node), (yyvsp[(3) - (3)].id))); } break; case 90: #line 875 "parse.y" { if (in_def || in_single) yyerror("dynamic constant assignment"); (yyval.node) = NEW_CDECL(0, 0, NEW_COLON3((yyvsp[(2) - (2)].id))); } break; case 91: #line 881 "parse.y" { rb_backref_error((yyvsp[(1) - (1)].node)); (yyval.node) = 0; } break; case 92: #line 888 "parse.y" { yyerror("class/module name must be CONSTANT"); } break; case 94: #line 895 "parse.y" { (yyval.node) = NEW_COLON3((yyvsp[(2) - (2)].id)); } break; case 95: #line 899 "parse.y" { (yyval.node) = NEW_COLON2(0, (yyval.node)); } break; case 96: #line 903 "parse.y" { (yyval.node) = NEW_COLON2((yyvsp[(1) - (3)].node), (yyvsp[(3) - (3)].id)); } break; case 100: #line 912 "parse.y" { lex_state = EXPR_END; (yyval.id) = (yyvsp[(1) - (1)].id); } break; case 101: #line 917 "parse.y" { lex_state = EXPR_END; (yyval.id) = (yyvsp[(1) - (1)].id); } break; case 104: #line 928 "parse.y" { (yyval.node) = NEW_LIT(ID2SYM((yyvsp[(1) - (1)].id))); } break; case 106: #line 935 "parse.y" { (yyval.node) = NEW_UNDEF((yyvsp[(1) - (1)].node)); } break; case 107: #line 938 "parse.y" {lex_state = EXPR_FNAME;} break; case 108: #line 939 "parse.y" { (yyval.node) = block_append((yyvsp[(1) - (4)].node), NEW_UNDEF((yyvsp[(4) - (4)].node))); } break; case 109: #line 944 "parse.y" { (yyval.id) = '|'; } break; case 110: #line 945 "parse.y" { (yyval.id) = '^'; } break; case 111: #line 946 "parse.y" { (yyval.id) = '&'; } break; case 112: #line 947 "parse.y" { (yyval.id) = tCMP; } break; case 113: #line 948 "parse.y" { (yyval.id) = tEQ; } break; case 114: #line 949 "parse.y" { (yyval.id) = tEQQ; } break; case 115: #line 950 "parse.y" { (yyval.id) = tMATCH; } break; case 116: #line 951 "parse.y" { (yyval.id) = '>'; } break; case 117: #line 952 "parse.y" { (yyval.id) = tGEQ; } break; case 118: #line 953 "parse.y" { (yyval.id) = '<'; } break; case 119: #line 954 "parse.y" { (yyval.id) = tLEQ; } break; case 120: #line 955 "parse.y" { (yyval.id) = tLSHFT; } break; case 121: #line 956 "parse.y" { (yyval.id) = tRSHFT; } break; case 122: #line 957 "parse.y" { (yyval.id) = '+'; } break; case 123: #line 958 "parse.y" { (yyval.id) = '-'; } break; case 124: #line 959 "parse.y" { (yyval.id) = '*'; } break; case 125: #line 960 "parse.y" { (yyval.id) = '*'; } break; case 126: #line 961 "parse.y" { (yyval.id) = '/'; } break; case 127: #line 962 "parse.y" { (yyval.id) = '%'; } break; case 128: #line 963 "parse.y" { (yyval.id) = tPOW; } break; case 129: #line 964 "parse.y" { (yyval.id) = '~'; } break; case 130: #line 965 "parse.y" { (yyval.id) = tUPLUS; } break; case 131: #line 966 "parse.y" { (yyval.id) = tUMINUS; } break; case 132: #line 967 "parse.y" { (yyval.id) = tAREF; } break; case 133: #line 968 "parse.y" { (yyval.id) = tASET; } break; case 134: #line 969 "parse.y" { (yyval.id) = '`'; } break; case 175: #line 982 "parse.y" { (yyval.node) = node_assign((yyvsp[(1) - (3)].node), (yyvsp[(3) - (3)].node)); } break; case 176: #line 986 "parse.y" { (yyval.node) = node_assign((yyvsp[(1) - (5)].node), NEW_RESCUE((yyvsp[(3) - (5)].node), NEW_RESBODY(0,(yyvsp[(5) - (5)].node),0), 0)); } break; case 177: #line 990 "parse.y" { value_expr((yyvsp[(3) - (3)].node)); if ((yyvsp[(1) - (3)].node)) { ID vid = (yyvsp[(1) - (3)].node)->nd_vid; if ((yyvsp[(2) - (3)].id) == tOROP) { (yyvsp[(1) - (3)].node)->nd_value = (yyvsp[(3) - (3)].node); (yyval.node) = NEW_OP_ASGN_OR(gettable(vid), (yyvsp[(1) - (3)].node)); if (is_asgn_or_id(vid)) { (yyval.node)->nd_aid = vid; } } else if ((yyvsp[(2) - (3)].id) == tANDOP) { (yyvsp[(1) - (3)].node)->nd_value = (yyvsp[(3) - (3)].node); (yyval.node) = NEW_OP_ASGN_AND(gettable(vid), (yyvsp[(1) - (3)].node)); } else { (yyval.node) = (yyvsp[(1) - (3)].node); (yyval.node)->nd_value = call_op(gettable(vid),(yyvsp[(2) - (3)].id),1,(yyvsp[(3) - (3)].node)); } } else { (yyval.node) = 0; } } break; case 178: #line 1015 "parse.y" { NODE *args; value_expr((yyvsp[(6) - (6)].node)); if (!(yyvsp[(3) - (6)].node)) (yyvsp[(3) - (6)].node) = NEW_ZARRAY(); args = arg_concat((yyvsp[(6) - (6)].node), (yyvsp[(3) - (6)].node)); if ((yyvsp[(5) - (6)].id) == tOROP) { (yyvsp[(5) - (6)].id) = 0; } else if ((yyvsp[(5) - (6)].id) == tANDOP) { (yyvsp[(5) - (6)].id) = 1; } (yyval.node) = NEW_OP_ASGN1((yyvsp[(1) - (6)].node), (yyvsp[(5) - (6)].id), args); fixpos((yyval.node), (yyvsp[(1) - (6)].node)); } break; case 179: #line 1031 "parse.y" { value_expr((yyvsp[(5) - (5)].node)); if ((yyvsp[(4) - (5)].id) == tOROP) { (yyvsp[(4) - (5)].id) = 0; } else if ((yyvsp[(4) - (5)].id) == tANDOP) { (yyvsp[(4) - (5)].id) = 1; } (yyval.node) = NEW_OP_ASGN2((yyvsp[(1) - (5)].node), (yyvsp[(3) - (5)].id), (yyvsp[(4) - (5)].id), (yyvsp[(5) - (5)].node)); fixpos((yyval.node), (yyvsp[(1) - (5)].node)); } break; case 180: #line 1043 "parse.y" { value_expr((yyvsp[(5) - (5)].node)); if ((yyvsp[(4) - (5)].id) == tOROP) { (yyvsp[(4) - (5)].id) = 0; } else if ((yyvsp[(4) - (5)].id) == tANDOP) { (yyvsp[(4) - (5)].id) = 1; } (yyval.node) = NEW_OP_ASGN2((yyvsp[(1) - (5)].node), (yyvsp[(3) - (5)].id), (yyvsp[(4) - (5)].id), (yyvsp[(5) - (5)].node)); fixpos((yyval.node), (yyvsp[(1) - (5)].node)); } break; case 181: #line 1055 "parse.y" { value_expr((yyvsp[(5) - (5)].node)); if ((yyvsp[(4) - (5)].id) == tOROP) { (yyvsp[(4) - (5)].id) = 0; } else if ((yyvsp[(4) - (5)].id) == tANDOP) { (yyvsp[(4) - (5)].id) = 1; } (yyval.node) = NEW_OP_ASGN2((yyvsp[(1) - (5)].node), (yyvsp[(3) - (5)].id), (yyvsp[(4) - (5)].id), (yyvsp[(5) - (5)].node)); fixpos((yyval.node), (yyvsp[(1) - (5)].node)); } break; case 182: #line 1067 "parse.y" { yyerror("constant re-assignment"); (yyval.node) = 0; } break; case 183: #line 1072 "parse.y" { yyerror("constant re-assignment"); (yyval.node) = 0; } break; case 184: #line 1077 "parse.y" { rb_backref_error((yyvsp[(1) - (3)].node)); (yyval.node) = 0; } break; case 185: #line 1082 "parse.y" { value_expr((yyvsp[(1) - (3)].node)); value_expr((yyvsp[(3) - (3)].node)); (yyval.node) = NEW_DOT2((yyvsp[(1) - (3)].node), (yyvsp[(3) - (3)].node)); if (nd_type((yyvsp[(1) - (3)].node)) == NODE_LIT && FIXNUM_P((yyvsp[(1) - (3)].node)->nd_lit) && nd_type((yyvsp[(3) - (3)].node)) == NODE_LIT && FIXNUM_P((yyvsp[(3) - (3)].node)->nd_lit)) { deferred_nodes = list_append(deferred_nodes, (yyval.node)); } } break; case 186: #line 1092 "parse.y" { value_expr((yyvsp[(1) - (3)].node)); value_expr((yyvsp[(3) - (3)].node)); (yyval.node) = NEW_DOT3((yyvsp[(1) - (3)].node), (yyvsp[(3) - (3)].node)); if (nd_type((yyvsp[(1) - (3)].node)) == NODE_LIT && FIXNUM_P((yyvsp[(1) - (3)].node)->nd_lit) && nd_type((yyvsp[(3) - (3)].node)) == NODE_LIT && FIXNUM_P((yyvsp[(3) - (3)].node)->nd_lit)) { deferred_nodes = list_append(deferred_nodes, (yyval.node)); } } break; case 187: #line 1102 "parse.y" { (yyval.node) = call_op((yyvsp[(1) - (3)].node), '+', 1, (yyvsp[(3) - (3)].node)); } break; case 188: #line 1106 "parse.y" { (yyval.node) = call_op((yyvsp[(1) - (3)].node), '-', 1, (yyvsp[(3) - (3)].node)); } break; case 189: #line 1110 "parse.y" { (yyval.node) = call_op((yyvsp[(1) - (3)].node), '*', 1, (yyvsp[(3) - (3)].node)); } break; case 190: #line 1114 "parse.y" { (yyval.node) = call_op((yyvsp[(1) - (3)].node), '/', 1, (yyvsp[(3) - (3)].node)); } break; case 191: #line 1118 "parse.y" { (yyval.node) = call_op((yyvsp[(1) - (3)].node), '%', 1, (yyvsp[(3) - (3)].node)); } break; case 192: #line 1122 "parse.y" { (yyval.node) = call_op((yyvsp[(1) - (3)].node), tPOW, 1, (yyvsp[(3) - (3)].node)); } break; case 193: #line 1126 "parse.y" { (yyval.node) = call_op(call_op((yyvsp[(2) - (4)].node), tPOW, 1, (yyvsp[(4) - (4)].node)), tUMINUS, 0, 0); } break; case 194: #line 1130 "parse.y" { (yyval.node) = call_op(call_op((yyvsp[(2) - (4)].node), tPOW, 1, (yyvsp[(4) - (4)].node)), tUMINUS, 0, 0); } break; case 195: #line 1134 "parse.y" { if ((yyvsp[(2) - (2)].node) && nd_type((yyvsp[(2) - (2)].node)) == NODE_LIT) { (yyval.node) = (yyvsp[(2) - (2)].node); } else { (yyval.node) = call_op((yyvsp[(2) - (2)].node), tUPLUS, 0, 0); } } break; case 196: #line 1143 "parse.y" { (yyval.node) = call_op((yyvsp[(2) - (2)].node), tUMINUS, 0, 0); } break; case 197: #line 1147 "parse.y" { (yyval.node) = call_op((yyvsp[(1) - (3)].node), '|', 1, (yyvsp[(3) - (3)].node)); } break; case 198: #line 1151 "parse.y" { (yyval.node) = call_op((yyvsp[(1) - (3)].node), '^', 1, (yyvsp[(3) - (3)].node)); } break; case 199: #line 1155 "parse.y" { (yyval.node) = call_op((yyvsp[(1) - (3)].node), '&', 1, (yyvsp[(3) - (3)].node)); } break; case 200: #line 1159 "parse.y" { (yyval.node) = call_op((yyvsp[(1) - (3)].node), tCMP, 1, (yyvsp[(3) - (3)].node)); } break; case 201: #line 1163 "parse.y" { (yyval.node) = call_op((yyvsp[(1) - (3)].node), '>', 1, (yyvsp[(3) - (3)].node)); } break; case 202: #line 1167 "parse.y" { (yyval.node) = call_op((yyvsp[(1) - (3)].node), tGEQ, 1, (yyvsp[(3) - (3)].node)); } break; case 203: #line 1171 "parse.y" { (yyval.node) = call_op((yyvsp[(1) - (3)].node), '<', 1, (yyvsp[(3) - (3)].node)); } break; case 204: #line 1175 "parse.y" { (yyval.node) = call_op((yyvsp[(1) - (3)].node), tLEQ, 1, (yyvsp[(3) - (3)].node)); } break; case 205: #line 1179 "parse.y" { (yyval.node) = call_op((yyvsp[(1) - (3)].node), tEQ, 1, (yyvsp[(3) - (3)].node)); } break; case 206: #line 1183 "parse.y" { (yyval.node) = call_op((yyvsp[(1) - (3)].node), tEQQ, 1, (yyvsp[(3) - (3)].node)); } break; case 207: #line 1187 "parse.y" { (yyval.node) = NEW_NOT(call_op((yyvsp[(1) - (3)].node), tEQ, 1, (yyvsp[(3) - (3)].node))); } break; case 208: #line 1191 "parse.y" { (yyval.node) = match_gen((yyvsp[(1) - (3)].node), (yyvsp[(3) - (3)].node)); } break; case 209: #line 1195 "parse.y" { (yyval.node) = NEW_NOT(match_gen((yyvsp[(1) - (3)].node), (yyvsp[(3) - (3)].node))); } break; case 210: #line 1199 "parse.y" { (yyval.node) = NEW_NOT(cond((yyvsp[(2) - (2)].node))); } break; case 211: #line 1203 "parse.y" { (yyval.node) = call_op((yyvsp[(2) - (2)].node), '~', 0, 0); } break; case 212: #line 1207 "parse.y" { (yyval.node) = call_op((yyvsp[(1) - (3)].node), tLSHFT, 1, (yyvsp[(3) - (3)].node)); } break; case 213: #line 1211 "parse.y" { (yyval.node) = call_op((yyvsp[(1) - (3)].node), tRSHFT, 1, (yyvsp[(3) - (3)].node)); } break; case 214: #line 1215 "parse.y" { (yyval.node) = logop(NODE_AND, (yyvsp[(1) - (3)].node), (yyvsp[(3) - (3)].node)); } break; case 215: #line 1219 "parse.y" { (yyval.node) = logop(NODE_OR, (yyvsp[(1) - (3)].node), (yyvsp[(3) - (3)].node)); } break; case 216: #line 1222 "parse.y" {in_defined = 1;} break; case 217: #line 1223 "parse.y" { in_defined = 0; (yyval.node) = NEW_DEFINED((yyvsp[(4) - (4)].node)); } break; case 218: #line 1228 "parse.y" { (yyval.node) = NEW_IF(cond((yyvsp[(1) - (5)].node)), (yyvsp[(3) - (5)].node), (yyvsp[(5) - (5)].node)); fixpos((yyval.node), (yyvsp[(1) - (5)].node)); } break; case 219: #line 1233 "parse.y" { (yyval.node) = (yyvsp[(1) - (1)].node); } break; case 220: #line 1239 "parse.y" { value_expr((yyvsp[(1) - (1)].node)); (yyval.node) = (yyvsp[(1) - (1)].node); } break; case 222: #line 1247 "parse.y" { rb_warn("parenthesize argument(s) for future version"); (yyval.node) = NEW_LIST((yyvsp[(1) - (2)].node)); } break; case 223: #line 1252 "parse.y" { (yyval.node) = (yyvsp[(1) - (2)].node); } break; case 224: #line 1256 "parse.y" { value_expr((yyvsp[(4) - (5)].node)); (yyval.node) = arg_concat((yyvsp[(1) - (5)].node), (yyvsp[(4) - (5)].node)); } break; case 225: #line 1261 "parse.y" { (yyval.node) = NEW_LIST(NEW_HASH((yyvsp[(1) - (2)].node))); } break; case 226: #line 1265 "parse.y" { value_expr((yyvsp[(2) - (3)].node)); (yyval.node) = NEW_NEWLINE(NEW_SPLAT((yyvsp[(2) - (3)].node))); } break; case 227: #line 1272 "parse.y" { (yyval.node) = (yyvsp[(2) - (3)].node); } break; case 228: #line 1276 "parse.y" { (yyval.node) = (yyvsp[(2) - (4)].node); } break; case 229: #line 1280 "parse.y" { rb_warn("parenthesize argument for future version"); (yyval.node) = NEW_LIST((yyvsp[(2) - (4)].node)); } break; case 230: #line 1285 "parse.y" { rb_warn("parenthesize argument for future version"); (yyval.node) = list_append((yyvsp[(2) - (6)].node), (yyvsp[(4) - (6)].node)); } break; case 233: #line 1296 "parse.y" { rb_warn("parenthesize argument(s) for future version"); (yyval.node) = NEW_LIST((yyvsp[(1) - (1)].node)); } break; case 234: #line 1301 "parse.y" { (yyval.node) = arg_blk_pass((yyvsp[(1) - (2)].node), (yyvsp[(2) - (2)].node)); } break; case 235: #line 1305 "parse.y" { (yyval.node) = arg_concat((yyvsp[(1) - (5)].node), (yyvsp[(4) - (5)].node)); (yyval.node) = arg_blk_pass((yyval.node), (yyvsp[(5) - (5)].node)); } break; case 236: #line 1310 "parse.y" { (yyval.node) = NEW_LIST(NEW_HASH((yyvsp[(1) - (2)].node))); (yyval.node) = arg_blk_pass((yyval.node), (yyvsp[(2) - (2)].node)); } break; case 237: #line 1315 "parse.y" { (yyval.node) = arg_concat(NEW_LIST(NEW_HASH((yyvsp[(1) - (5)].node))), (yyvsp[(4) - (5)].node)); (yyval.node) = arg_blk_pass((yyval.node), (yyvsp[(5) - (5)].node)); } break; case 238: #line 1320 "parse.y" { (yyval.node) = list_append((yyvsp[(1) - (4)].node), NEW_HASH((yyvsp[(3) - (4)].node))); (yyval.node) = arg_blk_pass((yyval.node), (yyvsp[(4) - (4)].node)); } break; case 239: #line 1325 "parse.y" { value_expr((yyvsp[(6) - (7)].node)); (yyval.node) = arg_concat(list_append((yyvsp[(1) - (7)].node), NEW_HASH((yyvsp[(3) - (7)].node))), (yyvsp[(6) - (7)].node)); (yyval.node) = arg_blk_pass((yyval.node), (yyvsp[(7) - (7)].node)); } break; case 240: #line 1331 "parse.y" { (yyval.node) = arg_blk_pass(NEW_SPLAT((yyvsp[(2) - (3)].node)), (yyvsp[(3) - (3)].node)); } break; case 242: #line 1338 "parse.y" { (yyval.node) = arg_blk_pass(list_concat(NEW_LIST((yyvsp[(1) - (4)].node)),(yyvsp[(3) - (4)].node)), (yyvsp[(4) - (4)].node)); } break; case 243: #line 1342 "parse.y" { (yyval.node) = arg_blk_pass((yyvsp[(1) - (3)].node), (yyvsp[(3) - (3)].node)); } break; case 244: #line 1346 "parse.y" { (yyval.node) = arg_concat(NEW_LIST((yyvsp[(1) - (5)].node)), (yyvsp[(4) - (5)].node)); (yyval.node) = arg_blk_pass((yyval.node), (yyvsp[(5) - (5)].node)); } break; case 245: #line 1351 "parse.y" { (yyval.node) = arg_concat(list_concat(NEW_LIST((yyvsp[(1) - (7)].node)),(yyvsp[(3) - (7)].node)), (yyvsp[(6) - (7)].node)); (yyval.node) = arg_blk_pass((yyval.node), (yyvsp[(7) - (7)].node)); } break; case 246: #line 1356 "parse.y" { (yyval.node) = NEW_LIST(NEW_HASH((yyvsp[(1) - (2)].node))); (yyval.node) = arg_blk_pass((yyval.node), (yyvsp[(2) - (2)].node)); } break; case 247: #line 1361 "parse.y" { (yyval.node) = arg_concat(NEW_LIST(NEW_HASH((yyvsp[(1) - (5)].node))), (yyvsp[(4) - (5)].node)); (yyval.node) = arg_blk_pass((yyval.node), (yyvsp[(5) - (5)].node)); } break; case 248: #line 1366 "parse.y" { (yyval.node) = list_append(NEW_LIST((yyvsp[(1) - (4)].node)), NEW_HASH((yyvsp[(3) - (4)].node))); (yyval.node) = arg_blk_pass((yyval.node), (yyvsp[(4) - (4)].node)); } break; case 249: #line 1371 "parse.y" { (yyval.node) = list_append(list_concat(NEW_LIST((yyvsp[(1) - (6)].node)),(yyvsp[(3) - (6)].node)), NEW_HASH((yyvsp[(5) - (6)].node))); (yyval.node) = arg_blk_pass((yyval.node), (yyvsp[(6) - (6)].node)); } break; case 250: #line 1376 "parse.y" { (yyval.node) = arg_concat(list_append(NEW_LIST((yyvsp[(1) - (7)].node)), NEW_HASH((yyvsp[(3) - (7)].node))), (yyvsp[(6) - (7)].node)); (yyval.node) = arg_blk_pass((yyval.node), (yyvsp[(7) - (7)].node)); } break; case 251: #line 1381 "parse.y" { (yyval.node) = arg_concat(list_append(list_concat(NEW_LIST((yyvsp[(1) - (9)].node)), (yyvsp[(3) - (9)].node)), NEW_HASH((yyvsp[(5) - (9)].node))), (yyvsp[(8) - (9)].node)); (yyval.node) = arg_blk_pass((yyval.node), (yyvsp[(9) - (9)].node)); } break; case 252: #line 1386 "parse.y" { (yyval.node) = arg_blk_pass(NEW_SPLAT((yyvsp[(2) - (3)].node)), (yyvsp[(3) - (3)].node)); } break; case 254: #line 1392 "parse.y" { (yyval.num) = cmdarg_stack; CMDARG_PUSH(1); } break; case 255: #line 1397 "parse.y" { /* CMDARG_POP() */ cmdarg_stack = (yyvsp[(1) - (2)].num); (yyval.node) = (yyvsp[(2) - (2)].node); } break; case 257: #line 1405 "parse.y" {lex_state = EXPR_ENDARG;} break; case 258: #line 1406 "parse.y" { rb_warn("don't put space before argument parentheses"); (yyval.node) = 0; } break; case 259: #line 1410 "parse.y" {lex_state = EXPR_ENDARG;} break; case 260: #line 1411 "parse.y" { rb_warn("don't put space before argument parentheses"); (yyval.node) = (yyvsp[(2) - (4)].node); } break; case 261: #line 1418 "parse.y" { (yyval.node) = NEW_BLOCK_PASS((yyvsp[(2) - (2)].node)); } break; case 262: #line 1424 "parse.y" { (yyval.node) = (yyvsp[(2) - (2)].node); } break; case 264: #line 1431 "parse.y" { (yyval.node) = NEW_LIST((yyvsp[(1) - (1)].node)); } break; case 265: #line 1435 "parse.y" { (yyval.node) = list_append((yyvsp[(1) - (3)].node), (yyvsp[(3) - (3)].node)); } break; case 266: #line 1441 "parse.y" { (yyval.node) = list_append((yyvsp[(1) - (3)].node), (yyvsp[(3) - (3)].node)); } break; case 267: #line 1445 "parse.y" { (yyval.node) = arg_concat((yyvsp[(1) - (4)].node), (yyvsp[(4) - (4)].node)); } break; case 268: #line 1449 "parse.y" { (yyval.node) = NEW_SPLAT((yyvsp[(2) - (2)].node)); } break; case 277: #line 1463 "parse.y" { (yyval.node) = NEW_FCALL((yyvsp[(1) - (1)].id), 0); } break; case 278: #line 1467 "parse.y" { (yyvsp[(1) - (1)].num) = ruby_sourceline; } break; case 279: #line 1472 "parse.y" { if ((yyvsp[(3) - (4)].node) == NULL) (yyval.node) = NEW_NIL(); else (yyval.node) = NEW_BEGIN((yyvsp[(3) - (4)].node)); nd_set_line((yyval.node), (yyvsp[(1) - (4)].num)); } break; case 280: #line 1479 "parse.y" {lex_state = EXPR_ENDARG;} break; case 281: #line 1480 "parse.y" { rb_warning("(...) interpreted as grouped expression"); (yyval.node) = (yyvsp[(2) - (5)].node); } break; case 282: #line 1485 "parse.y" { if (!(yyvsp[(2) - (3)].node)) (yyval.node) = NEW_NIL(); else (yyval.node) = (yyvsp[(2) - (3)].node); } break; case 283: #line 1490 "parse.y" { (yyval.node) = NEW_COLON2((yyvsp[(1) - (3)].node), (yyvsp[(3) - (3)].id)); } break; case 284: #line 1494 "parse.y" { (yyval.node) = NEW_COLON3((yyvsp[(2) - (2)].id)); } break; case 285: #line 1498 "parse.y" { if ((yyvsp[(1) - (4)].node) && nd_type((yyvsp[(1) - (4)].node)) == NODE_SELF) (yyval.node) = NEW_FCALL(tAREF, (yyvsp[(3) - (4)].node)); else (yyval.node) = NEW_CALL((yyvsp[(1) - (4)].node), tAREF, (yyvsp[(3) - (4)].node)); fixpos((yyval.node), (yyvsp[(1) - (4)].node)); } break; case 286: #line 1506 "parse.y" { if ((yyvsp[(2) - (3)].node) == 0) { (yyval.node) = NEW_ZARRAY(); /* zero length array*/ } else { (yyval.node) = (yyvsp[(2) - (3)].node); } } break; case 287: #line 1515 "parse.y" { (yyval.node) = NEW_HASH((yyvsp[(2) - (3)].node)); } break; case 288: #line 1519 "parse.y" { (yyval.node) = NEW_RETURN(0); } break; case 289: #line 1523 "parse.y" { (yyval.node) = new_yield((yyvsp[(3) - (4)].node)); } break; case 290: #line 1527 "parse.y" { (yyval.node) = NEW_YIELD(0, Qfalse); } break; case 291: #line 1531 "parse.y" { (yyval.node) = NEW_YIELD(0, Qfalse); } break; case 292: #line 1534 "parse.y" {in_defined = 1;} break; case 293: #line 1535 "parse.y" { in_defined = 0; (yyval.node) = NEW_DEFINED((yyvsp[(5) - (6)].node)); } break; case 294: #line 1540 "parse.y" { (yyvsp[(2) - (2)].node)->nd_iter = NEW_FCALL((yyvsp[(1) - (2)].id), 0); (yyval.node) = (yyvsp[(2) - (2)].node); fixpos((yyvsp[(2) - (2)].node)->nd_iter, (yyvsp[(2) - (2)].node)); } break; case 296: #line 1547 "parse.y" { if ((yyvsp[(1) - (2)].node) && nd_type((yyvsp[(1) - (2)].node)) == NODE_BLOCK_PASS) { rb_compile_error("both block arg and actual block given"); } (yyvsp[(2) - (2)].node)->nd_iter = (yyvsp[(1) - (2)].node); (yyval.node) = (yyvsp[(2) - (2)].node); fixpos((yyval.node), (yyvsp[(1) - (2)].node)); } break; case 297: #line 1559 "parse.y" { (yyval.node) = NEW_IF(cond((yyvsp[(2) - (6)].node)), (yyvsp[(4) - (6)].node), (yyvsp[(5) - (6)].node)); fixpos((yyval.node), (yyvsp[(2) - (6)].node)); if (cond_negative(&(yyval.node)->nd_cond)) { NODE *tmp = (yyval.node)->nd_body; (yyval.node)->nd_body = (yyval.node)->nd_else; (yyval.node)->nd_else = tmp; } } break; case 298: #line 1572 "parse.y" { (yyval.node) = NEW_UNLESS(cond((yyvsp[(2) - (6)].node)), (yyvsp[(4) - (6)].node), (yyvsp[(5) - (6)].node)); fixpos((yyval.node), (yyvsp[(2) - (6)].node)); if (cond_negative(&(yyval.node)->nd_cond)) { NODE *tmp = (yyval.node)->nd_body; (yyval.node)->nd_body = (yyval.node)->nd_else; (yyval.node)->nd_else = tmp; } } break; case 299: #line 1581 "parse.y" {COND_PUSH(1);} break; case 300: #line 1581 "parse.y" {COND_POP();} break; case 301: #line 1584 "parse.y" { (yyval.node) = NEW_WHILE(cond((yyvsp[(3) - (7)].node)), (yyvsp[(6) - (7)].node), 1); fixpos((yyval.node), (yyvsp[(3) - (7)].node)); if (cond_negative(&(yyval.node)->nd_cond)) { nd_set_type((yyval.node), NODE_UNTIL); } } break; case 302: #line 1591 "parse.y" {COND_PUSH(1);} break; case 303: #line 1591 "parse.y" {COND_POP();} break; case 304: #line 1594 "parse.y" { (yyval.node) = NEW_UNTIL(cond((yyvsp[(3) - (7)].node)), (yyvsp[(6) - (7)].node), 1); fixpos((yyval.node), (yyvsp[(3) - (7)].node)); if (cond_negative(&(yyval.node)->nd_cond)) { nd_set_type((yyval.node), NODE_WHILE); } } break; case 305: #line 1604 "parse.y" { (yyval.node) = NEW_CASE((yyvsp[(2) - (5)].node), (yyvsp[(4) - (5)].node)); fixpos((yyval.node), (yyvsp[(2) - (5)].node)); } break; case 306: #line 1609 "parse.y" { (yyval.node) = (yyvsp[(3) - (4)].node); } break; case 307: #line 1613 "parse.y" { (yyval.node) = (yyvsp[(4) - (5)].node); } break; case 308: #line 1616 "parse.y" {COND_PUSH(1);} break; case 309: #line 1616 "parse.y" {COND_POP();} break; case 310: #line 1619 "parse.y" { (yyval.node) = NEW_FOR((yyvsp[(2) - (9)].node), (yyvsp[(5) - (9)].node), (yyvsp[(8) - (9)].node)); fixpos((yyval.node), (yyvsp[(2) - (9)].node)); } break; case 311: #line 1624 "parse.y" { if (in_def || in_single) yyerror("class definition in method body"); class_nest++; local_push(0); (yyval.num) = ruby_sourceline; } break; case 312: #line 1633 "parse.y" { (yyval.node) = NEW_CLASS((yyvsp[(2) - (6)].node), (yyvsp[(5) - (6)].node), (yyvsp[(3) - (6)].node)); nd_set_line((yyval.node), (yyvsp[(4) - (6)].num)); local_pop(); class_nest--; } break; case 313: #line 1640 "parse.y" { (yyval.num) = in_def; in_def = 0; } break; case 314: #line 1645 "parse.y" { (yyval.num) = in_single; in_single = 0; class_nest++; local_push(0); } break; case 315: #line 1653 "parse.y" { (yyval.node) = NEW_SCLASS((yyvsp[(3) - (8)].node), (yyvsp[(7) - (8)].node)); fixpos((yyval.node), (yyvsp[(3) - (8)].node)); local_pop(); class_nest--; in_def = (yyvsp[(4) - (8)].num); in_single = (yyvsp[(6) - (8)].num); } break; case 316: #line 1662 "parse.y" { if (in_def || in_single) yyerror("module definition in method body"); class_nest++; local_push(0); (yyval.num) = ruby_sourceline; } break; case 317: #line 1671 "parse.y" { (yyval.node) = NEW_MODULE((yyvsp[(2) - (5)].node), (yyvsp[(4) - (5)].node)); nd_set_line((yyval.node), (yyvsp[(3) - (5)].num)); local_pop(); class_nest--; } break; case 318: #line 1678 "parse.y" { (yyval.id) = cur_mid; cur_mid = (yyvsp[(2) - (2)].id); in_def++; local_push(0); } break; case 319: #line 1687 "parse.y" { if (!(yyvsp[(5) - (6)].node)) (yyvsp[(5) - (6)].node) = NEW_NIL(); (yyval.node) = NEW_DEFN((yyvsp[(2) - (6)].id), (yyvsp[(4) - (6)].node), (yyvsp[(5) - (6)].node), NOEX_PRIVATE); fixpos((yyval.node), (yyvsp[(4) - (6)].node)); local_pop(); in_def--; cur_mid = (yyvsp[(3) - (6)].id); } break; case 320: #line 1695 "parse.y" {lex_state = EXPR_FNAME;} break; case 321: #line 1696 "parse.y" { in_single++; local_push(0); lex_state = EXPR_END; /* force for args */ } break; case 322: #line 1704 "parse.y" { (yyval.node) = NEW_DEFS((yyvsp[(2) - (9)].node), (yyvsp[(5) - (9)].id), (yyvsp[(7) - (9)].node), (yyvsp[(8) - (9)].node)); fixpos((yyval.node), (yyvsp[(2) - (9)].node)); local_pop(); in_single--; } break; case 323: #line 1711 "parse.y" { (yyval.node) = NEW_BREAK(0); } break; case 324: #line 1715 "parse.y" { (yyval.node) = NEW_NEXT(0); } break; case 325: #line 1719 "parse.y" { (yyval.node) = NEW_REDO(); } break; case 326: #line 1723 "parse.y" { (yyval.node) = NEW_RETRY(); } break; case 327: #line 1729 "parse.y" { value_expr((yyvsp[(1) - (1)].node)); (yyval.node) = (yyvsp[(1) - (1)].node); } break; case 336: #line 1750 "parse.y" { (yyval.node) = NEW_IF(cond((yyvsp[(2) - (5)].node)), (yyvsp[(4) - (5)].node), (yyvsp[(5) - (5)].node)); fixpos((yyval.node), (yyvsp[(2) - (5)].node)); } break; case 338: #line 1758 "parse.y" { (yyval.node) = (yyvsp[(2) - (2)].node); } break; case 342: #line 1769 "parse.y" { (yyval.node) = (NODE*)1; } break; case 343: #line 1773 "parse.y" { (yyval.node) = (NODE*)1; } break; case 344: #line 1777 "parse.y" { (yyval.node) = (yyvsp[(2) - (3)].node); } break; case 345: #line 1783 "parse.y" { (yyval.vars) = dyna_push(); (yyvsp[(1) - (1)].num) = ruby_sourceline; } break; case 346: #line 1787 "parse.y" {(yyval.vars) = ruby_dyna_vars;} break; case 347: #line 1790 "parse.y" { (yyval.node) = NEW_ITER((yyvsp[(3) - (6)].node), 0, dyna_init((yyvsp[(5) - (6)].node), (yyvsp[(4) - (6)].vars))); nd_set_line((yyval.node), (yyvsp[(1) - (6)].num)); dyna_pop((yyvsp[(2) - (6)].vars)); } break; case 348: #line 1798 "parse.y" { if ((yyvsp[(1) - (2)].node) && nd_type((yyvsp[(1) - (2)].node)) == NODE_BLOCK_PASS) { rb_compile_error("both block arg and actual block given"); } (yyvsp[(2) - (2)].node)->nd_iter = (yyvsp[(1) - (2)].node); (yyval.node) = (yyvsp[(2) - (2)].node); fixpos((yyval.node), (yyvsp[(1) - (2)].node)); } break; case 349: #line 1807 "parse.y" { (yyval.node) = new_call((yyvsp[(1) - (4)].node), (yyvsp[(3) - (4)].id), (yyvsp[(4) - (4)].node)); } break; case 350: #line 1811 "parse.y" { (yyval.node) = new_call((yyvsp[(1) - (4)].node), (yyvsp[(3) - (4)].id), (yyvsp[(4) - (4)].node)); } break; case 351: #line 1817 "parse.y" { (yyval.node) = new_fcall((yyvsp[(1) - (2)].id), (yyvsp[(2) - (2)].node)); fixpos((yyval.node), (yyvsp[(2) - (2)].node)); } break; case 352: #line 1822 "parse.y" { (yyval.node) = new_call((yyvsp[(1) - (4)].node), (yyvsp[(3) - (4)].id), (yyvsp[(4) - (4)].node)); fixpos((yyval.node), (yyvsp[(1) - (4)].node)); } break; case 353: #line 1827 "parse.y" { (yyval.node) = new_call((yyvsp[(1) - (4)].node), (yyvsp[(3) - (4)].id), (yyvsp[(4) - (4)].node)); fixpos((yyval.node), (yyvsp[(1) - (4)].node)); } break; case 354: #line 1832 "parse.y" { (yyval.node) = new_call((yyvsp[(1) - (3)].node), (yyvsp[(3) - (3)].id), 0); } break; case 355: #line 1836 "parse.y" { (yyval.node) = new_super((yyvsp[(2) - (2)].node)); } break; case 356: #line 1840 "parse.y" { (yyval.node) = NEW_ZSUPER(); } break; case 357: #line 1846 "parse.y" { (yyval.vars) = dyna_push(); (yyvsp[(1) - (1)].num) = ruby_sourceline; } break; case 358: #line 1850 "parse.y" {(yyval.vars) = ruby_dyna_vars;} break; case 359: #line 1852 "parse.y" { (yyval.node) = NEW_ITER((yyvsp[(3) - (6)].node), 0, dyna_init((yyvsp[(5) - (6)].node), (yyvsp[(4) - (6)].vars))); nd_set_line((yyval.node), (yyvsp[(1) - (6)].num)); dyna_pop((yyvsp[(2) - (6)].vars)); } break; case 360: #line 1858 "parse.y" { (yyval.vars) = dyna_push(); (yyvsp[(1) - (1)].num) = ruby_sourceline; } break; case 361: #line 1862 "parse.y" {(yyval.vars) = ruby_dyna_vars;} break; case 362: #line 1864 "parse.y" { (yyval.node) = NEW_ITER((yyvsp[(3) - (6)].node), 0, dyna_init((yyvsp[(5) - (6)].node), (yyvsp[(4) - (6)].vars))); nd_set_line((yyval.node), (yyvsp[(1) - (6)].num)); dyna_pop((yyvsp[(2) - (6)].vars)); } break; case 363: #line 1874 "parse.y" { (yyval.node) = NEW_WHEN((yyvsp[(2) - (5)].node), (yyvsp[(4) - (5)].node), (yyvsp[(5) - (5)].node)); } break; case 365: #line 1880 "parse.y" { (yyval.node) = list_append((yyvsp[(1) - (4)].node), NEW_WHEN((yyvsp[(4) - (4)].node), 0, 0)); } break; case 366: #line 1884 "parse.y" { (yyval.node) = NEW_LIST(NEW_WHEN((yyvsp[(2) - (2)].node), 0, 0)); } break; case 369: #line 1896 "parse.y" { if ((yyvsp[(3) - (6)].node)) { (yyvsp[(3) - (6)].node) = node_assign((yyvsp[(3) - (6)].node), NEW_GVAR(rb_intern("$!"))); (yyvsp[(5) - (6)].node) = block_append((yyvsp[(3) - (6)].node), (yyvsp[(5) - (6)].node)); } (yyval.node) = NEW_RESBODY((yyvsp[(2) - (6)].node), (yyvsp[(5) - (6)].node), (yyvsp[(6) - (6)].node)); fixpos((yyval.node), (yyvsp[(2) - (6)].node)?(yyvsp[(2) - (6)].node):(yyvsp[(5) - (6)].node)); } break; case 371: #line 1908 "parse.y" { (yyval.node) = NEW_LIST((yyvsp[(1) - (1)].node)); } break; case 374: #line 1916 "parse.y" { (yyval.node) = (yyvsp[(2) - (2)].node); } break; case 376: #line 1923 "parse.y" { if ((yyvsp[(2) - (2)].node)) (yyval.node) = (yyvsp[(2) - (2)].node); else /* place holder */ (yyval.node) = NEW_NIL(); } break; case 379: #line 1935 "parse.y" { (yyval.node) = NEW_LIT(ID2SYM((yyvsp[(1) - (1)].id))); } break; case 381: #line 1942 "parse.y" { NODE *node = (yyvsp[(1) - (1)].node); if (!node) { node = NEW_STR(rb_str_new(0, 0)); } else { node = evstr2dstr(node); } (yyval.node) = node; } break; case 383: #line 1956 "parse.y" { (yyval.node) = literal_concat((yyvsp[(1) - (2)].node), (yyvsp[(2) - (2)].node)); } break; case 384: #line 1962 "parse.y" { (yyval.node) = (yyvsp[(2) - (3)].node); } break; case 385: #line 1968 "parse.y" { NODE *node = (yyvsp[(2) - (3)].node); if (!node) { node = NEW_XSTR(rb_str_new(0, 0)); } else { switch (nd_type(node)) { case NODE_STR: nd_set_type(node, NODE_XSTR); break; case NODE_DSTR: nd_set_type(node, NODE_DXSTR); break; default: node = NEW_NODE(NODE_DXSTR, rb_str_new(0, 0), 1, NEW_LIST(node)); break; } } (yyval.node) = node; } break; case 386: #line 1991 "parse.y" { int options = (yyvsp[(3) - (3)].num); NODE *node = (yyvsp[(2) - (3)].node); if (!node) { node = NEW_LIT(rb_reg_new("", 0, options & ~RE_OPTION_ONCE)); } else switch (nd_type(node)) { case NODE_STR: { VALUE src = node->nd_lit; nd_set_type(node, NODE_LIT); node->nd_lit = rb_reg_new(RSTRING(src)->ptr, RSTRING(src)->len, options & ~RE_OPTION_ONCE); } break; default: node = NEW_NODE(NODE_DSTR, rb_str_new(0, 0), 1, NEW_LIST(node)); case NODE_DSTR: if (options & RE_OPTION_ONCE) { nd_set_type(node, NODE_DREGX_ONCE); } else { nd_set_type(node, NODE_DREGX); } node->nd_cflag = options & ~RE_OPTION_ONCE; break; } (yyval.node) = node; } break; case 387: #line 2024 "parse.y" { (yyval.node) = NEW_ZARRAY(); } break; case 388: #line 2028 "parse.y" { (yyval.node) = (yyvsp[(2) - (3)].node); } break; case 389: #line 2034 "parse.y" { (yyval.node) = 0; } break; case 390: #line 2038 "parse.y" { (yyval.node) = list_append((yyvsp[(1) - (3)].node), evstr2dstr((yyvsp[(2) - (3)].node))); } break; case 392: #line 2045 "parse.y" { (yyval.node) = literal_concat((yyvsp[(1) - (2)].node), (yyvsp[(2) - (2)].node)); } break; case 393: #line 2051 "parse.y" { (yyval.node) = NEW_ZARRAY(); } break; case 394: #line 2055 "parse.y" { (yyval.node) = (yyvsp[(2) - (3)].node); } break; case 395: #line 2061 "parse.y" { (yyval.node) = 0; } break; case 396: #line 2065 "parse.y" { (yyval.node) = list_append((yyvsp[(1) - (3)].node), (yyvsp[(2) - (3)].node)); } break; case 397: #line 2071 "parse.y" { (yyval.node) = 0; } break; case 398: #line 2075 "parse.y" { (yyval.node) = literal_concat((yyvsp[(1) - (2)].node), (yyvsp[(2) - (2)].node)); } break; case 399: #line 2081 "parse.y" { (yyval.node) = 0; } break; case 400: #line 2085 "parse.y" { (yyval.node) = literal_concat((yyvsp[(1) - (2)].node), (yyvsp[(2) - (2)].node)); } break; case 402: #line 2092 "parse.y" { (yyval.node) = lex_strterm; lex_strterm = 0; lex_state = EXPR_BEG; } break; case 403: #line 2098 "parse.y" { lex_strterm = (yyvsp[(2) - (3)].node); (yyval.node) = NEW_EVSTR((yyvsp[(3) - (3)].node)); } break; case 404: #line 2103 "parse.y" { (yyval.node) = lex_strterm; lex_strterm = 0; lex_state = EXPR_BEG; COND_PUSH(0); CMDARG_PUSH(0); } break; case 405: #line 2111 "parse.y" { lex_strterm = (yyvsp[(2) - (4)].node); COND_LEXPOP(); CMDARG_LEXPOP(); if (((yyval.node) = (yyvsp[(3) - (4)].node)) && nd_type((yyval.node)) == NODE_NEWLINE) { (yyval.node) = (yyval.node)->nd_next; rb_gc_force_recycle((VALUE)(yyvsp[(3) - (4)].node)); } (yyval.node) = new_evstr((yyval.node)); } break; case 406: #line 2123 "parse.y" {(yyval.node) = NEW_GVAR((yyvsp[(1) - (1)].id));} break; case 407: #line 2124 "parse.y" {(yyval.node) = NEW_IVAR((yyvsp[(1) - (1)].id));} break; case 408: #line 2125 "parse.y" {(yyval.node) = NEW_CVAR((yyvsp[(1) - (1)].id));} break; case 410: #line 2130 "parse.y" { lex_state = EXPR_END; (yyval.id) = (yyvsp[(2) - (2)].id); } break; case 415: #line 2143 "parse.y" { lex_state = EXPR_END; if (!((yyval.node) = (yyvsp[(2) - (3)].node))) { (yyval.node) = NEW_NIL(); yyerror("empty symbol literal"); } else { VALUE lit; switch (nd_type((yyval.node))) { case NODE_DSTR: nd_set_type((yyval.node), NODE_DSYM); break; case NODE_STR: lit = (yyval.node)->nd_lit; if (RSTRING(lit)->len == 0) { yyerror("empty symbol literal"); break; } if (strlen(RSTRING(lit)->ptr) == RSTRING(lit)->len) { (yyval.node)->nd_lit = ID2SYM(rb_intern(RSTRING((yyval.node)->nd_lit)->ptr)); nd_set_type((yyval.node), NODE_LIT); break; } /* fall through */ default: (yyval.node) = NEW_NODE(NODE_DSYM, rb_str_new(0, 0), 1, NEW_LIST((yyval.node))); break; } } } break; case 418: #line 2179 "parse.y" { (yyval.node) = negate_lit((yyvsp[(2) - (2)].node)); } break; case 419: #line 2183 "parse.y" { (yyval.node) = negate_lit((yyvsp[(2) - (2)].node)); } break; case 425: #line 2193 "parse.y" {(yyval.id) = kNIL;} break; case 426: #line 2194 "parse.y" {(yyval.id) = kSELF;} break; case 427: #line 2195 "parse.y" {(yyval.id) = kTRUE;} break; case 428: #line 2196 "parse.y" {(yyval.id) = kFALSE;} break; case 429: #line 2197 "parse.y" {(yyval.id) = k__FILE__;} break; case 430: #line 2198 "parse.y" {(yyval.id) = k__LINE__;} break; case 431: #line 2202 "parse.y" { (yyval.node) = gettable((yyvsp[(1) - (1)].id)); } break; case 432: #line 2208 "parse.y" { (yyval.node) = assignable((yyvsp[(1) - (1)].id), 0); } break; case 435: #line 2218 "parse.y" { (yyval.node) = 0; } break; case 436: #line 2222 "parse.y" { lex_state = EXPR_BEG; } break; case 437: #line 2226 "parse.y" { (yyval.node) = (yyvsp[(3) - (4)].node); } break; case 438: #line 2229 "parse.y" {yyerrok; (yyval.node) = 0;} break; case 439: #line 2233 "parse.y" { (yyval.node) = (yyvsp[(2) - (4)].node); lex_state = EXPR_BEG; command_start = Qtrue; } break; case 440: #line 2239 "parse.y" { (yyval.node) = (yyvsp[(1) - (2)].node); } break; case 441: #line 2245 "parse.y" { (yyval.node) = block_append(NEW_ARGS((yyvsp[(1) - (6)].num), (yyvsp[(3) - (6)].node), (yyvsp[(5) - (6)].node)), (yyvsp[(6) - (6)].node)); } break; case 442: #line 2249 "parse.y" { (yyval.node) = block_append(NEW_ARGS((yyvsp[(1) - (4)].num), (yyvsp[(3) - (4)].node), 0), (yyvsp[(4) - (4)].node)); } break; case 443: #line 2253 "parse.y" { (yyval.node) = block_append(NEW_ARGS((yyvsp[(1) - (4)].num), 0, (yyvsp[(3) - (4)].node)), (yyvsp[(4) - (4)].node)); } break; case 444: #line 2257 "parse.y" { (yyval.node) = block_append(NEW_ARGS((yyvsp[(1) - (2)].num), 0, 0), (yyvsp[(2) - (2)].node)); } break; case 445: #line 2261 "parse.y" { (yyval.node) = block_append(NEW_ARGS(0, (yyvsp[(1) - (4)].node), (yyvsp[(3) - (4)].node)), (yyvsp[(4) - (4)].node)); } break; case 446: #line 2265 "parse.y" { (yyval.node) = block_append(NEW_ARGS(0, (yyvsp[(1) - (2)].node), 0), (yyvsp[(2) - (2)].node)); } break; case 447: #line 2269 "parse.y" { (yyval.node) = block_append(NEW_ARGS(0, 0, (yyvsp[(1) - (2)].node)), (yyvsp[(2) - (2)].node)); } break; case 448: #line 2273 "parse.y" { (yyval.node) = block_append(NEW_ARGS(0, 0, 0), (yyvsp[(1) - (1)].node)); } break; case 449: #line 2277 "parse.y" { (yyval.node) = NEW_ARGS(0, 0, 0); } break; case 450: #line 2283 "parse.y" { yyerror("formal argument cannot be a constant"); } break; case 451: #line 2287 "parse.y" { yyerror("formal argument cannot be an instance variable"); } break; case 452: #line 2291 "parse.y" { yyerror("formal argument cannot be a global variable"); } break; case 453: #line 2295 "parse.y" { yyerror("formal argument cannot be a class variable"); } break; case 454: #line 2299 "parse.y" { if (!is_local_id((yyvsp[(1) - (1)].id))) yyerror("formal argument must be local variable"); else if (local_id((yyvsp[(1) - (1)].id))) yyerror("duplicate argument name"); local_cnt((yyvsp[(1) - (1)].id)); (yyval.num) = 1; } break; case 456: #line 2311 "parse.y" { (yyval.num) += 1; } break; case 457: #line 2317 "parse.y" { if (!is_local_id((yyvsp[(1) - (3)].id))) yyerror("formal argument must be local variable"); else if (local_id((yyvsp[(1) - (3)].id))) yyerror("duplicate optional argument name"); (yyval.node) = assignable((yyvsp[(1) - (3)].id), (yyvsp[(3) - (3)].node)); } break; case 458: #line 2327 "parse.y" { (yyval.node) = NEW_BLOCK((yyvsp[(1) - (1)].node)); (yyval.node)->nd_end = (yyval.node); } break; case 459: #line 2332 "parse.y" { (yyval.node) = block_append((yyvsp[(1) - (3)].node), (yyvsp[(3) - (3)].node)); } break; case 462: #line 2342 "parse.y" { if (!is_local_id((yyvsp[(2) - (2)].id))) yyerror("rest argument must be local variable"); else if (local_id((yyvsp[(2) - (2)].id))) yyerror("duplicate rest argument name"); if (dyna_in_block()) { rb_dvar_push((yyvsp[(2) - (2)].id), Qnil); } (yyval.node) = assignable((yyvsp[(2) - (2)].id), 0); } break; case 463: #line 2353 "parse.y" { if (dyna_in_block()) { (yyval.node) = NEW_DASGN_CURR(internal_id(), 0); } else { (yyval.node) = NEW_NODE(NODE_LASGN,0,0,local_append(0)); } } break; case 466: #line 2368 "parse.y" { if (!is_local_id((yyvsp[(2) - (2)].id))) yyerror("block argument must be local variable"); else if (local_id((yyvsp[(2) - (2)].id))) yyerror("duplicate block argument name"); (yyval.node) = NEW_BLOCK_ARG((yyvsp[(2) - (2)].id)); } break; case 467: #line 2378 "parse.y" { (yyval.node) = (yyvsp[(2) - (2)].node); } break; case 469: #line 2385 "parse.y" { (yyval.node) = (yyvsp[(1) - (1)].node); value_expr((yyval.node)); } break; case 470: #line 2389 "parse.y" {lex_state = EXPR_BEG;} break; case 471: #line 2390 "parse.y" { if ((yyvsp[(3) - (5)].node) == 0) { yyerror("can't define singleton method for ()."); } else { switch (nd_type((yyvsp[(3) - (5)].node))) { case NODE_STR: case NODE_DSTR: case NODE_XSTR: case NODE_DXSTR: case NODE_DREGX: case NODE_LIT: case NODE_ARRAY: case NODE_ZARRAY: yyerror("can't define singleton method for literals"); default: value_expr((yyvsp[(3) - (5)].node)); break; } } (yyval.node) = (yyvsp[(3) - (5)].node); } break; case 473: #line 2416 "parse.y" { (yyval.node) = (yyvsp[(1) - (2)].node); } break; case 474: #line 2420 "parse.y" { if ((yyvsp[(1) - (2)].node)->nd_alen%2 != 0) { yyerror("odd number list for Hash"); } (yyval.node) = (yyvsp[(1) - (2)].node); } break; case 476: #line 2430 "parse.y" { (yyval.node) = list_concat((yyvsp[(1) - (3)].node), (yyvsp[(3) - (3)].node)); } break; case 477: #line 2436 "parse.y" { (yyval.node) = list_append(NEW_LIST((yyvsp[(1) - (3)].node)), (yyvsp[(3) - (3)].node)); } break; case 497: #line 2474 "parse.y" {yyerrok;} break; case 500: #line 2479 "parse.y" {yyerrok;} break; case 501: #line 2482 "parse.y" {(yyval.node) = 0;} break; /* Line 1267 of yacc.c. */ #line 7259 "parse.c" default: break; } YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc); YYPOPSTACK (yylen); yylen = 0; YY_STACK_PRINT (yyss, yyssp); *++yyvsp = yyval; /* Now `shift' the result of the reduction. Determine what state that goes to, based on the state we popped back to and the rule number reduced by. */ yyn = yyr1[yyn]; yystate = yypgoto[yyn - YYNTOKENS] + *yyssp; if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp) yystate = yytable[yystate]; else yystate = yydefgoto[yyn - YYNTOKENS]; goto yynewstate; /*------------------------------------. | yyerrlab -- here on detecting error | `------------------------------------*/ yyerrlab: /* If not already recovering from an error, report this error. */ if (!yyerrstatus) { ++yynerrs; #if ! YYERROR_VERBOSE yyerror (YY_("syntax error")); #else { YYSIZE_T yysize = yysyntax_error (0, yystate, yychar); if (yymsg_alloc < yysize && yymsg_alloc < YYSTACK_ALLOC_MAXIMUM) { YYSIZE_T yyalloc = 2 * yysize; if (! (yysize <= yyalloc && yyalloc <= YYSTACK_ALLOC_MAXIMUM)) yyalloc = YYSTACK_ALLOC_MAXIMUM; if (yymsg != yymsgbuf) YYSTACK_FREE (yymsg); yymsg = (char *) YYSTACK_ALLOC (yyalloc); if (yymsg) yymsg_alloc = yyalloc; else { yymsg = yymsgbuf; yymsg_alloc = sizeof yymsgbuf; } } if (0 < yysize && yysize <= yymsg_alloc) { (void) yysyntax_error (yymsg, yystate, yychar); yyerror (yymsg); } else { yyerror (YY_("syntax error")); if (yysize != 0) goto yyexhaustedlab; } } #endif } if (yyerrstatus == 3) { /* If just tried and failed to reuse look-ahead token after an error, discard it. */ if (yychar <= YYEOF) { /* Return failure if at end of input. */ if (yychar == YYEOF) YYABORT; } else { yydestruct ("Error: discarding", yytoken, &yylval); yychar = YYEMPTY; } } /* Else will try to reuse look-ahead token after shifting the error token. */ goto yyerrlab1; /*---------------------------------------------------. | yyerrorlab -- error raised explicitly by YYERROR. | `---------------------------------------------------*/ yyerrorlab: /* Pacify compilers like GCC when the user code never invokes YYERROR and the label yyerrorlab therefore never appears in user code. */ if (/*CONSTCOND*/ 0) goto yyerrorlab; /* Do not reclaim the symbols of the rule which action triggered this YYERROR. */ YYPOPSTACK (yylen); yylen = 0; YY_STACK_PRINT (yyss, yyssp); yystate = *yyssp; goto yyerrlab1; /*-------------------------------------------------------------. | yyerrlab1 -- common code for both syntax error and YYERROR. | `-------------------------------------------------------------*/ yyerrlab1: yyerrstatus = 3; /* Each real token shifted decrements this. */ for (;;) { yyn = yypact[yystate]; if (yyn != YYPACT_NINF) { yyn += YYTERROR; if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR) { yyn = yytable[yyn]; if (0 < yyn) break; } } /* Pop the current state because it cannot handle the error token. */ if (yyssp == yyss) YYABORT; yydestruct ("Error: popping", yystos[yystate], yyvsp); YYPOPSTACK (1); yystate = *yyssp; YY_STACK_PRINT (yyss, yyssp); } if (yyn == YYFINAL) YYACCEPT; *++yyvsp = yylval; /* Shift the error token. */ YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp); yystate = yyn; goto yynewstate; /*-------------------------------------. | yyacceptlab -- YYACCEPT comes here. | `-------------------------------------*/ yyacceptlab: yyresult = 0; goto yyreturn; /*-----------------------------------. | yyabortlab -- YYABORT comes here. | `-----------------------------------*/ yyabortlab: yyresult = 1; goto yyreturn; #ifndef yyoverflow /*-------------------------------------------------. | yyexhaustedlab -- memory exhaustion comes here. | `-------------------------------------------------*/ yyexhaustedlab: yyerror (YY_("memory exhausted")); yyresult = 2; /* Fall through. */ #endif yyreturn: if (yychar != YYEOF && yychar != YYEMPTY) yydestruct ("Cleanup: discarding lookahead", yytoken, &yylval); /* Do not reclaim the symbols of the rule which action triggered this YYABORT or YYACCEPT. */ YYPOPSTACK (yylen); YY_STACK_PRINT (yyss, yyssp); while (yyssp != yyss) { yydestruct ("Cleanup: popping", yystos[*yyssp], yyvsp); YYPOPSTACK (1); } #ifndef yyoverflow if (yyss != yyssa) YYSTACK_FREE (yyss); #endif #if YYERROR_VERBOSE if (yymsg != yymsgbuf) YYSTACK_FREE (yymsg); #endif /* Make sure YYID is used. */ return YYID (yyresult); } #line 2484 "parse.y" #ifdef yystacksize #undef YYMALLOC #endif #include "regex.h" #include "util.h" /* We remove any previous definition of `SIGN_EXTEND_CHAR', since ours (we hope) works properly with all combinations of machines, compilers, `char' and `unsigned char' argument types. (Per Bothner suggested the basic approach.) */ #undef SIGN_EXTEND_CHAR #if __STDC__ # define SIGN_EXTEND_CHAR(c) ((signed char)(c)) #else /* not __STDC__ */ /* As in Harbison and Steele. */ # define SIGN_EXTEND_CHAR(c) ((((unsigned char)(c)) ^ 128) - 128) #endif #define is_identchar(c) (SIGN_EXTEND_CHAR(c)!=-1&&(ISALNUM(c) || (c) == '_' || ismbchar(c))) static char *tokenbuf = NULL; static int tokidx, toksiz = 0; #define LEAVE_BS 1 static VALUE (*lex_gets)(); /* gets function */ static VALUE lex_input; /* non-nil if File */ static VALUE lex_lastline; /* gc protect */ static char *lex_pbeg; static char *lex_p; static char *lex_pend; static int yyerror(msg) const char *msg; { const int max_line_margin = 30; const char *p, *pe; char *buf; int len, i; rb_compile_error("%s", msg); p = lex_p; while (lex_pbeg <= p) { if (*p == '\n') break; p--; } p++; pe = lex_p; while (pe < lex_pend) { if (*pe == '\n') break; pe++; } len = pe - p; if (len > 4) { char *p2; const char *pre = "", *post = ""; if (len > max_line_margin * 2 + 10) { int re_mbc_startpos _((const char *, int, int, int)); if ((len = lex_p - p) > max_line_margin) { p = p + re_mbc_startpos(p, len, len - max_line_margin, 0); pre = "..."; } if ((len = pe - lex_p) > max_line_margin) { pe = lex_p + re_mbc_startpos(lex_p, len, max_line_margin, 1); post = "..."; } len = pe - p; } buf = ALLOCA_N(char, len+2); MEMCPY(buf, p, char, len); buf[len] = '\0'; rb_compile_error_append("%s%s%s", pre, buf, post); i = lex_p - p; p2 = buf; pe = buf + len; while (p2 < pe) { if (*p2 != '\t') *p2 = ' '; p2++; } buf[i] = '^'; buf[i+1] = '\0'; rb_compile_error_append("%s", buf); } return 0; } static int heredoc_end; int ruby_in_compile = 0; int ruby__end__seen; static VALUE ruby_debug_lines; #ifdef YYMALLOC static NODE *parser_heap; #endif static NODE* yycompile(f, line) char *f; int line; { int n; NODE *node = 0; struct RVarmap *vp, *vars = ruby_dyna_vars; ruby_in_compile = 1; if (!compile_for_eval && rb_safe_level() == 0 && rb_const_defined(rb_cObject, rb_intern("SCRIPT_LINES__"))) { VALUE hash, fname; hash = rb_const_get(rb_cObject, rb_intern("SCRIPT_LINES__")); if (TYPE(hash) == T_HASH) { fname = rb_str_new2(f); ruby_debug_lines = rb_ary_new(); rb_hash_aset(hash, fname, ruby_debug_lines); } if (line > 1) { VALUE str = rb_str_new(0,0); while (line > 1) { rb_ary_push(ruby_debug_lines, str); line--; } } } ruby__end__seen = 0; ruby_eval_tree = 0; ruby_eval_tree_begin = 0; heredoc_end = 0; lex_strterm = 0; ruby_current_node = 0; ruby_sourcefile = rb_source_filename(f); deferred_nodes = 0; n = yyparse(); ruby_debug_lines = 0; compile_for_eval = 0; ruby_in_compile = 0; cond_stack = 0; cmdarg_stack = 0; command_start = 1; class_nest = 0; in_single = 0; in_def = 0; cur_mid = 0; deferred_nodes = 0; vp = ruby_dyna_vars; ruby_dyna_vars = vars; lex_strterm = 0; while (vp && vp != vars) { struct RVarmap *tmp = vp; vp = vp->next; rb_gc_force_recycle((VALUE)tmp); } if (n == 0) node = ruby_eval_tree; if (ruby_nerrs) ruby_eval_tree_begin = 0; return node; } static int lex_gets_ptr; static VALUE lex_get_str(s) VALUE s; { char *beg, *end, *pend; beg = RSTRING(s)->ptr; if (lex_gets_ptr) { if (RSTRING(s)->len == lex_gets_ptr) return Qnil; beg += lex_gets_ptr; } pend = RSTRING(s)->ptr + RSTRING(s)->len; end = beg; while (end < pend) { if (*end++ == '\n') break; } lex_gets_ptr = end - RSTRING(s)->ptr; return rb_str_new(beg, end - beg); } static VALUE lex_getline() { VALUE line = (*lex_gets)(lex_input); if (ruby_debug_lines && !NIL_P(line)) { rb_ary_push(ruby_debug_lines, line); } return line; } NODE* rb_compile_string(f, s, line) const char *f; VALUE s; int line; { lex_gets = lex_get_str; lex_gets_ptr = 0; lex_input = s; lex_pbeg = lex_p = lex_pend = 0; ruby_sourceline = line - 1; compile_for_eval = ruby_in_eval; return yycompile(f, line); } NODE* rb_compile_cstr(f, s, len, line) const char *f, *s; int len, line; { return rb_compile_string(f, rb_str_new(s, len), line); } NODE* rb_compile_file(f, file, start) const char *f; VALUE file; int start; { lex_gets = rb_io_gets; lex_input = file; lex_pbeg = lex_p = lex_pend = 0; ruby_sourceline = start - 1; return yycompile(f, start); } static inline int nextc() { int c; if (lex_p == lex_pend) { if (lex_input) { VALUE v = lex_getline(); if (NIL_P(v)) return -1; if (heredoc_end > 0) { ruby_sourceline = heredoc_end; heredoc_end = 0; } ruby_sourceline++; lex_pbeg = lex_p = RSTRING(v)->ptr; lex_pend = lex_p + RSTRING(v)->len; lex_lastline = v; } else { lex_lastline = 0; return -1; } } c = (unsigned char)*lex_p++; if (c == '\r' && lex_p < lex_pend && *lex_p == '\n') { lex_p++; c = '\n'; } return c; } static void pushback(c) int c; { if (c == -1) return; lex_p--; } #define was_bol() (lex_p == lex_pbeg + 1) #define peek(c) (lex_p != lex_pend && (c) == *lex_p) #define tokfix() (tokenbuf[tokidx]='\0') #define tok() tokenbuf #define toklen() tokidx #define toklast() (tokidx>0?tokenbuf[tokidx-1]:0) static char* newtok() { tokidx = 0; if (!tokenbuf) { toksiz = 60; tokenbuf = ALLOC_N(char, 60); } if (toksiz > 4096) { toksiz = 60; REALLOC_N(tokenbuf, char, 60); } return tokenbuf; } static void tokadd(c) char c; { tokenbuf[tokidx++] = c; if (tokidx >= toksiz) { toksiz *= 2; REALLOC_N(tokenbuf, char, toksiz); } } static int read_escape() { int c; switch (c = nextc()) { case '\\': /* Backslash */ return c; case 'n': /* newline */ return '\n'; case 't': /* horizontal tab */ return '\t'; case 'r': /* carriage-return */ return '\r'; case 'f': /* form-feed */ return '\f'; case 'v': /* vertical tab */ return '\13'; case 'a': /* alarm(bell) */ return '\007'; case 'e': /* escape */ return 033; case '0': case '1': case '2': case '3': /* octal constant */ case '4': case '5': case '6': case '7': { int numlen; pushback(c); c = scan_oct(lex_p, 3, &numlen); lex_p += numlen; } return c; case 'x': /* hex constant */ { int numlen; c = scan_hex(lex_p, 2, &numlen); if (numlen == 0) { yyerror("Invalid escape character syntax"); return 0; } lex_p += numlen; } return c; case 'b': /* backspace */ return '\010'; case 's': /* space */ return ' '; case 'M': if ((c = nextc()) != '-') { yyerror("Invalid escape character syntax"); pushback(c); return '\0'; } if ((c = nextc()) == '\\') { return read_escape() | 0x80; } else if (c == -1) goto eof; else { return ((c & 0xff) | 0x80); } case 'C': if ((c = nextc()) != '-') { yyerror("Invalid escape character syntax"); pushback(c); return '\0'; } case 'c': if ((c = nextc())== '\\') { c = read_escape(); } else if (c == '?') return 0177; else if (c == -1) goto eof; return c & 0x9f; eof: case -1: yyerror("Invalid escape character syntax"); return '\0'; default: return c; } } static int tokadd_escape(term) int term; { int c; switch (c = nextc()) { case '\n': return 0; /* just ignore */ case '0': case '1': case '2': case '3': /* octal constant */ case '4': case '5': case '6': case '7': { int i; tokadd('\\'); tokadd(c); for (i=0; i<2; i++) { c = nextc(); if (c == -1) goto eof; if (c < '0' || '7' < c) { pushback(c); break; } tokadd(c); } } return 0; case 'x': /* hex constant */ { int numlen; tokadd('\\'); tokadd(c); scan_hex(lex_p, 2, &numlen); if (numlen == 0) { yyerror("Invalid escape character syntax"); return -1; } while (numlen--) tokadd(nextc()); } return 0; case 'M': if ((c = nextc()) != '-') { yyerror("Invalid escape character syntax"); pushback(c); return 0; } tokadd('\\'); tokadd('M'); tokadd('-'); goto escaped; case 'C': if ((c = nextc()) != '-') { yyerror("Invalid escape character syntax"); pushback(c); return 0; } tokadd('\\'); tokadd('C'); tokadd('-'); goto escaped; case 'c': tokadd('\\'); tokadd('c'); escaped: if ((c = nextc()) == '\\') { return tokadd_escape(term); } else if (c == -1) goto eof; tokadd(c); return 0; eof: case -1: yyerror("Invalid escape character syntax"); return -1; default: if (c != '\\' || c != term) tokadd('\\'); tokadd(c); } return 0; } static int regx_options() { char kcode = 0; int options = 0; int c; newtok(); while (c = nextc(), ISALPHA(c)) { switch (c) { case 'i': options |= RE_OPTION_IGNORECASE; break; case 'x': options |= RE_OPTION_EXTENDED; break; case 'm': options |= RE_OPTION_MULTILINE; break; case 'o': options |= RE_OPTION_ONCE; break; case 'n': kcode = 16; break; case 'e': kcode = 32; break; case 's': kcode = 48; break; case 'u': kcode = 64; break; default: tokadd(c); break; } } pushback(c); if (toklen()) { tokfix(); rb_compile_error("unknown regexp option%s - %s", toklen() > 1 ? "s" : "", tok()); } return options | kcode; } #define STR_FUNC_ESCAPE 0x01 #define STR_FUNC_EXPAND 0x02 #define STR_FUNC_REGEXP 0x04 #define STR_FUNC_QWORDS 0x08 #define STR_FUNC_SYMBOL 0x10 #define STR_FUNC_INDENT 0x20 enum string_type { str_squote = (0), str_dquote = (STR_FUNC_EXPAND), str_xquote = (STR_FUNC_EXPAND), str_regexp = (STR_FUNC_REGEXP|STR_FUNC_ESCAPE|STR_FUNC_EXPAND), str_sword = (STR_FUNC_QWORDS), str_dword = (STR_FUNC_QWORDS|STR_FUNC_EXPAND), str_ssym = (STR_FUNC_SYMBOL), str_dsym = (STR_FUNC_SYMBOL|STR_FUNC_EXPAND), }; static void dispose_string(str) VALUE str; { xfree(RSTRING(str)->ptr); rb_gc_force_recycle(str); } static int tokadd_string(func, term, paren, nest) int func, term, paren, *nest; { int c; while ((c = nextc()) != -1) { if (paren && c == paren) { ++*nest; } else if (c == term) { if (!nest || !*nest) { pushback(c); break; } --*nest; } else if ((func & STR_FUNC_EXPAND) && c == '#' && lex_p < lex_pend) { int c2 = *lex_p; if (c2 == '$' || c2 == '@' || c2 == '{') { pushback(c); break; } } else if (c == '\\') { c = nextc(); switch (c) { case '\n': if (func & STR_FUNC_QWORDS) break; if (func & STR_FUNC_EXPAND) continue; tokadd('\\'); break; case '\\': if (func & STR_FUNC_ESCAPE) tokadd(c); break; default: if (func & STR_FUNC_REGEXP) { pushback(c); if (tokadd_escape(term) < 0) return -1; continue; } else if (func & STR_FUNC_EXPAND) { pushback(c); if (func & STR_FUNC_ESCAPE) tokadd('\\'); c = read_escape(); } else if ((func & STR_FUNC_QWORDS) && ISSPACE(c)) { /* ignore backslashed spaces in %w */ } else if (c != term && !(paren && c == paren)) { tokadd('\\'); } } } else if (ismbchar(c)) { int i, len = mbclen(c)-1; for (i = 0; i < len; i++) { tokadd(c); c = nextc(); } } else if ((func & STR_FUNC_QWORDS) && ISSPACE(c)) { pushback(c); break; } if (!c && (func & STR_FUNC_SYMBOL)) { func &= ~STR_FUNC_SYMBOL; rb_compile_error("symbol cannot contain '\\0'"); continue; } tokadd(c); } return c; } #define NEW_STRTERM(func, term, paren) \ rb_node_newnode(NODE_STRTERM, (func), (term) | ((paren) << (CHAR_BIT * 2)), 0) static int parse_string(quote) NODE *quote; { int func = quote->nd_func; int term = nd_term(quote); int paren = nd_paren(quote); int c, space = 0; if (func == -1) return tSTRING_END; c = nextc(); if ((func & STR_FUNC_QWORDS) && ISSPACE(c)) { do {c = nextc();} while (ISSPACE(c)); space = 1; } if (c == term && !quote->nd_nest) { if (func & STR_FUNC_QWORDS) { quote->nd_func = -1; return ' '; } if (!(func & STR_FUNC_REGEXP)) return tSTRING_END; yylval.num = regx_options(); return tREGEXP_END; } if (space) { pushback(c); return ' '; } newtok(); if ((func & STR_FUNC_EXPAND) && c == '#') { switch (c = nextc()) { case '$': case '@': pushback(c); return tSTRING_DVAR; case '{': return tSTRING_DBEG; } tokadd('#'); } pushback(c); if (tokadd_string(func, term, paren, "e->nd_nest) == -1) { ruby_sourceline = nd_line(quote); rb_compile_error("unterminated string meets end of file"); return tSTRING_END; } tokfix(); yylval.node = NEW_STR(rb_str_new(tok(), toklen())); return tSTRING_CONTENT; } static int heredoc_identifier() { int c = nextc(), term, func = 0, len; if (c == '-') { c = nextc(); func = STR_FUNC_INDENT; } switch (c) { case '\'': func |= str_squote; goto quoted; case '"': func |= str_dquote; goto quoted; case '`': func |= str_xquote; quoted: newtok(); tokadd(func); term = c; while ((c = nextc()) != -1 && c != term) { len = mbclen(c); do {tokadd(c);} while (--len > 0 && (c = nextc()) != -1); } if (c == -1) { rb_compile_error("unterminated here document identifier"); return 0; } break; default: if (!is_identchar(c)) { pushback(c); if (func & STR_FUNC_INDENT) { pushback('-'); } return 0; } newtok(); term = '"'; tokadd(func |= str_dquote); do { len = mbclen(c); do {tokadd(c);} while (--len > 0 && (c = nextc()) != -1); } while ((c = nextc()) != -1 && is_identchar(c)); pushback(c); break; } tokfix(); len = lex_p - lex_pbeg; lex_p = lex_pend; lex_strterm = rb_node_newnode(NODE_HEREDOC, rb_str_new(tok(), toklen()), /* nd_lit */ len, /* nd_nth */ lex_lastline); /* nd_orig */ return term == '`' ? tXSTRING_BEG : tSTRING_BEG; } static void heredoc_restore(here) NODE *here; { VALUE line = here->nd_orig; lex_lastline = line; lex_pbeg = RSTRING(line)->ptr; lex_pend = lex_pbeg + RSTRING(line)->len; lex_p = lex_pbeg + here->nd_nth; heredoc_end = ruby_sourceline; ruby_sourceline = nd_line(here); dispose_string(here->nd_lit); rb_gc_force_recycle((VALUE)here); } static int whole_match_p(eos, len, indent) char *eos; int len, indent; { char *p = lex_pbeg; int n; if (indent) { while (*p && ISSPACE(*p)) p++; } n= lex_pend - (p + len); if (n < 0 || (n > 0 && p[len] != '\n' && p[len] != '\r')) return Qfalse; if (strncmp(eos, p, len) == 0) return Qtrue; return Qfalse; } static int here_document(here) NODE *here; { int c, func, indent = 0; char *eos, *p, *pend; long len; VALUE str = 0; eos = RSTRING(here->nd_lit)->ptr; len = RSTRING(here->nd_lit)->len - 1; indent = (func = *eos++) & STR_FUNC_INDENT; if ((c = nextc()) == -1) { error: rb_compile_error("can't find string \"%s\" anywhere before EOF", eos); heredoc_restore(lex_strterm); lex_strterm = 0; return 0; } if (was_bol() && whole_match_p(eos, len, indent)) { heredoc_restore(lex_strterm); return tSTRING_END; } if (!(func & STR_FUNC_EXPAND)) { do { p = RSTRING(lex_lastline)->ptr; pend = lex_pend; if (pend > p) { switch (pend[-1]) { case '\n': if (--pend == p || pend[-1] != '\r') { pend++; break; } case '\r': --pend; } } if (str) rb_str_cat(str, p, pend - p); else str = rb_str_new(p, pend - p); if (pend < lex_pend) rb_str_cat(str, "\n", 1); lex_p = lex_pend; if (nextc() == -1) { if (str) dispose_string(str); goto error; } } while (!whole_match_p(eos, len, indent)); } else { newtok(); if (c == '#') { switch (c = nextc()) { case '$': case '@': pushback(c); return tSTRING_DVAR; case '{': return tSTRING_DBEG; } tokadd('#'); } do { pushback(c); if ((c = tokadd_string(func, '\n', 0, NULL)) == -1) goto error; if (c != '\n') { yylval.node = NEW_STR(rb_str_new(tok(), toklen())); return tSTRING_CONTENT; } tokadd(nextc()); if ((c = nextc()) == -1) goto error; } while (!whole_match_p(eos, len, indent)); str = rb_str_new(tok(), toklen()); } heredoc_restore(lex_strterm); lex_strterm = NEW_STRTERM(-1, 0, 0); yylval.node = NEW_STR(str); return tSTRING_CONTENT; } #include "lex.c" static void arg_ambiguous() { rb_warning("ambiguous first argument; put parentheses or even spaces"); } #define IS_ARG() (lex_state == EXPR_ARG || lex_state == EXPR_CMDARG) #define IS_BEG() (lex_state == EXPR_BEG || lex_state == EXPR_MID || lex_state == EXPR_CLASS) static int yylex() { register int c; int space_seen = 0; int cmd_state; enum lex_state last_state; if (lex_strterm) { int token; if (nd_type(lex_strterm) == NODE_HEREDOC) { token = here_document(lex_strterm); if (token == tSTRING_END) { lex_strterm = 0; lex_state = EXPR_END; } } else { token = parse_string(lex_strterm); if (token == tSTRING_END || token == tREGEXP_END) { rb_gc_force_recycle((VALUE)lex_strterm); lex_strterm = 0; lex_state = EXPR_END; } } return token; } cmd_state = command_start; command_start = Qfalse; retry: switch (c = nextc()) { case '\0': /* NUL */ case '\004': /* ^D */ case '\032': /* ^Z */ case -1: /* end of script. */ return 0; /* white spaces */ case ' ': case '\t': case '\f': case '\r': case '\13': /* '\v' */ space_seen++; goto retry; case '#': /* it's a comment */ while ((c = nextc()) != '\n') { if (c == -1) return 0; } /* fall through */ case '\n': switch (lex_state) { case EXPR_BEG: case EXPR_FNAME: case EXPR_DOT: case EXPR_CLASS: goto retry; default: break; } command_start = Qtrue; lex_state = EXPR_BEG; return '\n'; case '*': if ((c = nextc()) == '*') { if ((c = nextc()) == '=') { yylval.id = tPOW; lex_state = EXPR_BEG; return tOP_ASGN; } pushback(c); c = tPOW; } else { if (c == '=') { yylval.id = '*'; lex_state = EXPR_BEG; return tOP_ASGN; } pushback(c); if (IS_ARG() && space_seen && !ISSPACE(c)){ rb_warning("`*' interpreted as argument prefix"); c = tSTAR; } else if (IS_BEG()) { c = tSTAR; } else { c = '*'; } } switch (lex_state) { case EXPR_FNAME: case EXPR_DOT: lex_state = EXPR_ARG; break; default: lex_state = EXPR_BEG; break; } return c; case '!': lex_state = EXPR_BEG; if ((c = nextc()) == '=') { return tNEQ; } if (c == '~') { return tNMATCH; } pushback(c); return '!'; case '=': if (was_bol()) { /* skip embedded rd document */ if (strncmp(lex_p, "begin", 5) == 0 && ISSPACE(lex_p[5])) { for (;;) { lex_p = lex_pend; c = nextc(); if (c == -1) { rb_compile_error("embedded document meets end of file"); return 0; } if (c != '=') continue; if (strncmp(lex_p, "end", 3) == 0 && (lex_p + 3 == lex_pend || ISSPACE(lex_p[3]))) { break; } } lex_p = lex_pend; goto retry; } } switch (lex_state) { case EXPR_FNAME: case EXPR_DOT: lex_state = EXPR_ARG; break; default: lex_state = EXPR_BEG; break; } if ((c = nextc()) == '=') { if ((c = nextc()) == '=') { return tEQQ; } pushback(c); return tEQ; } if (c == '~') { return tMATCH; } else if (c == '>') { return tASSOC; } pushback(c); return '='; case '<': c = nextc(); if (c == '<' && lex_state != EXPR_END && lex_state != EXPR_DOT && lex_state != EXPR_ENDARG && lex_state != EXPR_CLASS && (!IS_ARG() || space_seen)) { int token = heredoc_identifier(); if (token) return token; } switch (lex_state) { case EXPR_FNAME: case EXPR_DOT: lex_state = EXPR_ARG; break; default: lex_state = EXPR_BEG; break; } if (c == '=') { if ((c = nextc()) == '>') { return tCMP; } pushback(c); return tLEQ; } if (c == '<') { if ((c = nextc()) == '=') { yylval.id = tLSHFT; lex_state = EXPR_BEG; return tOP_ASGN; } pushback(c); return tLSHFT; } pushback(c); return '<'; case '>': switch (lex_state) { case EXPR_FNAME: case EXPR_DOT: lex_state = EXPR_ARG; break; default: lex_state = EXPR_BEG; break; } if ((c = nextc()) == '=') { return tGEQ; } if (c == '>') { if ((c = nextc()) == '=') { yylval.id = tRSHFT; lex_state = EXPR_BEG; return tOP_ASGN; } pushback(c); return tRSHFT; } pushback(c); return '>'; case '"': lex_strterm = NEW_STRTERM(str_dquote, '"', 0); return tSTRING_BEG; case '`': if (lex_state == EXPR_FNAME) { lex_state = EXPR_END; return c; } if (lex_state == EXPR_DOT) { if (cmd_state) lex_state = EXPR_CMDARG; else lex_state = EXPR_ARG; return c; } lex_strterm = NEW_STRTERM(str_xquote, '`', 0); return tXSTRING_BEG; case '\'': lex_strterm = NEW_STRTERM(str_squote, '\'', 0); return tSTRING_BEG; case '?': if (lex_state == EXPR_END || lex_state == EXPR_ENDARG) { lex_state = EXPR_BEG; return '?'; } c = nextc(); if (c == -1) { rb_compile_error("incomplete character syntax"); return 0; } if (ISSPACE(c)){ if (!IS_ARG()){ int c2 = 0; switch (c) { case ' ': c2 = 's'; break; case '\n': c2 = 'n'; break; case '\t': c2 = 't'; break; case '\v': c2 = 'v'; break; case '\r': c2 = 'r'; break; case '\f': c2 = 'f'; break; } if (c2) { rb_warn("invalid character syntax; use ?\\%c", c2); } } ternary: pushback(c); lex_state = EXPR_BEG; return '?'; } else if (ismbchar(c)) { rb_warn("multibyte character literal not supported yet; use ?\\%.3o", c); goto ternary; } else if ((ISALNUM(c) || c == '_') && lex_p < lex_pend && is_identchar(*lex_p)) { goto ternary; } else if (c == '\\') { c = read_escape(); } c &= 0xff; lex_state = EXPR_END; yylval.node = NEW_LIT(INT2FIX(c)); return tINTEGER; case '&': if ((c = nextc()) == '&') { lex_state = EXPR_BEG; if ((c = nextc()) == '=') { yylval.id = tANDOP; lex_state = EXPR_BEG; return tOP_ASGN; } pushback(c); return tANDOP; } else if (c == '=') { yylval.id = '&'; lex_state = EXPR_BEG; return tOP_ASGN; } pushback(c); if (IS_ARG() && space_seen && !ISSPACE(c)){ rb_warning("`&' interpreted as argument prefix"); c = tAMPER; } else if (IS_BEG()) { c = tAMPER; } else { c = '&'; } switch (lex_state) { case EXPR_FNAME: case EXPR_DOT: lex_state = EXPR_ARG; break; default: lex_state = EXPR_BEG; } return c; case '|': if ((c = nextc()) == '|') { lex_state = EXPR_BEG; if ((c = nextc()) == '=') { yylval.id = tOROP; lex_state = EXPR_BEG; return tOP_ASGN; } pushback(c); return tOROP; } if (c == '=') { yylval.id = '|'; lex_state = EXPR_BEG; return tOP_ASGN; } if (lex_state == EXPR_FNAME || lex_state == EXPR_DOT) { lex_state = EXPR_ARG; } else { lex_state = EXPR_BEG; } pushback(c); return '|'; case '+': c = nextc(); if (lex_state == EXPR_FNAME || lex_state == EXPR_DOT) { lex_state = EXPR_ARG; if (c == '@') { return tUPLUS; } pushback(c); return '+'; } if (c == '=') { yylval.id = '+'; lex_state = EXPR_BEG; return tOP_ASGN; } if (IS_BEG() || (IS_ARG() && space_seen && !ISSPACE(c))) { if (IS_ARG()) arg_ambiguous(); lex_state = EXPR_BEG; pushback(c); if (ISDIGIT(c)) { c = '+'; goto start_num; } return tUPLUS; } lex_state = EXPR_BEG; pushback(c); return '+'; case '-': c = nextc(); if (lex_state == EXPR_FNAME || lex_state == EXPR_DOT) { lex_state = EXPR_ARG; if (c == '@') { return tUMINUS; } pushback(c); return '-'; } if (c == '=') { yylval.id = '-'; lex_state = EXPR_BEG; return tOP_ASGN; } if (IS_BEG() || (IS_ARG() && space_seen && !ISSPACE(c))) { if (IS_ARG()) arg_ambiguous(); lex_state = EXPR_BEG; pushback(c); if (ISDIGIT(c)) { return tUMINUS_NUM; } return tUMINUS; } lex_state = EXPR_BEG; pushback(c); return '-'; case '.': lex_state = EXPR_BEG; if ((c = nextc()) == '.') { if ((c = nextc()) == '.') { return tDOT3; } pushback(c); return tDOT2; } pushback(c); if (ISDIGIT(c)) { yyerror("no . floating literal anymore; put 0 before dot"); } lex_state = EXPR_DOT; return '.'; start_num: case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { int is_float, seen_point, seen_e, nondigit; is_float = seen_point = seen_e = nondigit = 0; lex_state = EXPR_END; newtok(); if (c == '-' || c == '+') { tokadd(c); c = nextc(); } if (c == '0') { int start = toklen(); c = nextc(); if (c == 'x' || c == 'X') { /* hexadecimal */ c = nextc(); if (ISXDIGIT(c)) { do { if (c == '_') { if (nondigit) break; nondigit = c; continue; } if (!ISXDIGIT(c)) break; nondigit = 0; tokadd(c); } while ((c = nextc()) != -1); } pushback(c); tokfix(); if (toklen() == start) { yyerror("numeric literal without digits"); } else if (nondigit) goto trailing_uc; yylval.node = NEW_LIT(rb_cstr_to_inum(tok(), 16, Qfalse)); return tINTEGER; } if (c == 'b' || c == 'B') { /* binary */ c = nextc(); if (c == '0' || c == '1') { do { if (c == '_') { if (nondigit) break; nondigit = c; continue; } if (c != '0' && c != '1') break; nondigit = 0; tokadd(c); } while ((c = nextc()) != -1); } pushback(c); tokfix(); if (toklen() == start) { yyerror("numeric literal without digits"); } else if (nondigit) goto trailing_uc; yylval.node = NEW_LIT(rb_cstr_to_inum(tok(), 2, Qfalse)); return tINTEGER; } if (c == 'd' || c == 'D') { /* decimal */ c = nextc(); if (ISDIGIT(c)) { do { if (c == '_') { if (nondigit) break; nondigit = c; continue; } if (!ISDIGIT(c)) break; nondigit = 0; tokadd(c); } while ((c = nextc()) != -1); } pushback(c); tokfix(); if (toklen() == start) { yyerror("numeric literal without digits"); } else if (nondigit) goto trailing_uc; yylval.node = NEW_LIT(rb_cstr_to_inum(tok(), 10, Qfalse)); return tINTEGER; } if (c == '_') { /* 0_0 */ goto octal_number; } if (c == 'o' || c == 'O') { /* prefixed octal */ c = nextc(); if (c == '_') { yyerror("numeric literal without digits"); } } if (c >= '0' && c <= '7') { /* octal */ octal_number: do { if (c == '_') { if (nondigit) break; nondigit = c; continue; } if (c < '0' || c > '9') break; if (c > '7') goto invalid_octal; nondigit = 0; tokadd(c); } while ((c = nextc()) != -1); if (toklen() > start) { pushback(c); tokfix(); if (nondigit) goto trailing_uc; yylval.node = NEW_LIT(rb_cstr_to_inum(tok(), 8, Qfalse)); return tINTEGER; } if (nondigit) { pushback(c); goto trailing_uc; } } if (c > '7' && c <= '9') { invalid_octal: yyerror("Illegal octal digit"); } else if (c == '.' || c == 'e' || c == 'E') { tokadd('0'); } else { pushback(c); yylval.node = NEW_LIT(INT2FIX(0)); return tINTEGER; } } for (;;) { switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': nondigit = 0; tokadd(c); break; case '.': if (nondigit) goto trailing_uc; if (seen_point || seen_e) { goto decode_num; } else { int c0 = nextc(); if (!ISDIGIT(c0)) { pushback(c0); goto decode_num; } c = c0; } tokadd('.'); tokadd(c); is_float++; seen_point++; nondigit = 0; break; case 'e': case 'E': if (nondigit) { pushback(c); c = nondigit; goto decode_num; } if (seen_e) { goto decode_num; } tokadd(c); seen_e++; is_float++; nondigit = c; c = nextc(); if (c != '-' && c != '+') continue; tokadd(c); nondigit = c; break; case '_': /* `_' in number just ignored */ if (nondigit) goto decode_num; nondigit = c; break; default: goto decode_num; } c = nextc(); } decode_num: pushback(c); tokfix(); if (nondigit) { char tmp[30]; trailing_uc: sprintf(tmp, "trailing `%c' in number", nondigit); yyerror(tmp); } if (is_float) { double d = strtod(tok(), 0); if (errno == ERANGE) { rb_warn("Float %s out of range", tok()); errno = 0; } yylval.node = NEW_LIT(rb_float_new(d)); return tFLOAT; } yylval.node = NEW_LIT(rb_cstr_to_inum(tok(), 10, Qfalse)); return tINTEGER; } case ']': case '}': case ')': COND_LEXPOP(); CMDARG_LEXPOP(); lex_state = EXPR_END; return c; case ':': c = nextc(); if (c == ':') { if (IS_BEG() || (IS_ARG() && space_seen)) { lex_state = EXPR_BEG; return tCOLON3; } lex_state = EXPR_DOT; return tCOLON2; } if (lex_state == EXPR_END || lex_state == EXPR_ENDARG || ISSPACE(c)) { pushback(c); lex_state = EXPR_BEG; return ':'; } switch (c) { case '\'': lex_strterm = NEW_STRTERM(str_ssym, c, 0); break; case '"': lex_strterm = NEW_STRTERM(str_dsym, c, 0); break; default: pushback(c); break; } lex_state = EXPR_FNAME; return tSYMBEG; case '/': if (IS_BEG()) { lex_strterm = NEW_STRTERM(str_regexp, '/', 0); return tREGEXP_BEG; } if ((c = nextc()) == '=') { yylval.id = '/'; lex_state = EXPR_BEG; return tOP_ASGN; } pushback(c); if (IS_ARG() && space_seen) { if (!ISSPACE(c)) { arg_ambiguous(); lex_strterm = NEW_STRTERM(str_regexp, '/', 0); return tREGEXP_BEG; } } switch (lex_state) { case EXPR_FNAME: case EXPR_DOT: lex_state = EXPR_ARG; break; default: lex_state = EXPR_BEG; break; } return '/'; case '^': if ((c = nextc()) == '=') { yylval.id = '^'; lex_state = EXPR_BEG; return tOP_ASGN; } switch (lex_state) { case EXPR_FNAME: case EXPR_DOT: lex_state = EXPR_ARG; break; default: lex_state = EXPR_BEG; break; } pushback(c); return '^'; case ';': command_start = Qtrue; case ',': lex_state = EXPR_BEG; return c; case '~': if (lex_state == EXPR_FNAME || lex_state == EXPR_DOT) { if ((c = nextc()) != '@') { pushback(c); } } switch (lex_state) { case EXPR_FNAME: case EXPR_DOT: lex_state = EXPR_ARG; break; default: lex_state = EXPR_BEG; break; } return '~'; case '(': command_start = Qtrue; if (IS_BEG()) { c = tLPAREN; } else if (space_seen) { if (lex_state == EXPR_CMDARG) { c = tLPAREN_ARG; } else if (lex_state == EXPR_ARG) { rb_warn("don't put space before argument parentheses"); c = '('; } } COND_PUSH(0); CMDARG_PUSH(0); lex_state = EXPR_BEG; return c; case '[': if (lex_state == EXPR_FNAME || lex_state == EXPR_DOT) { lex_state = EXPR_ARG; if ((c = nextc()) == ']') { if ((c = nextc()) == '=') { return tASET; } pushback(c); return tAREF; } pushback(c); return '['; } else if (IS_BEG()) { c = tLBRACK; } else if (IS_ARG() && space_seen) { c = tLBRACK; } lex_state = EXPR_BEG; COND_PUSH(0); CMDARG_PUSH(0); return c; case '{': if (IS_ARG() || lex_state == EXPR_END) c = '{'; /* block (primary) */ else if (lex_state == EXPR_ENDARG) c = tLBRACE_ARG; /* block (expr) */ else c = tLBRACE; /* hash */ COND_PUSH(0); CMDARG_PUSH(0); lex_state = EXPR_BEG; return c; case '\\': c = nextc(); if (c == '\n') { space_seen = 1; goto retry; /* skip \\n */ } pushback(c); return '\\'; case '%': if (IS_BEG()) { int term; int paren; c = nextc(); quotation: if (!ISALNUM(c)) { term = c; c = 'Q'; } else { term = nextc(); if (ISALNUM(term) || ismbchar(term)) { yyerror("unknown type of %string"); return 0; } } if (c == -1 || term == -1) { rb_compile_error("unterminated quoted string meets end of file"); return 0; } paren = term; if (term == '(') term = ')'; else if (term == '[') term = ']'; else if (term == '{') term = '}'; else if (term == '<') term = '>'; else paren = 0; switch (c) { case 'Q': lex_strterm = NEW_STRTERM(str_dquote, term, paren); return tSTRING_BEG; case 'q': lex_strterm = NEW_STRTERM(str_squote, term, paren); return tSTRING_BEG; case 'W': lex_strterm = NEW_STRTERM(str_dword, term, paren); do {c = nextc();} while (ISSPACE(c)); pushback(c); return tWORDS_BEG; case 'w': lex_strterm = NEW_STRTERM(str_sword, term, paren); do {c = nextc();} while (ISSPACE(c)); pushback(c); return tQWORDS_BEG; case 'x': lex_strterm = NEW_STRTERM(str_xquote, term, paren); return tXSTRING_BEG; case 'r': lex_strterm = NEW_STRTERM(str_regexp, term, paren); return tREGEXP_BEG; case 's': lex_strterm = NEW_STRTERM(str_ssym, term, paren); lex_state = EXPR_FNAME; return tSYMBEG; default: yyerror("unknown type of %string"); return 0; } } if ((c = nextc()) == '=') { yylval.id = '%'; lex_state = EXPR_BEG; return tOP_ASGN; } if (IS_ARG() && space_seen && !ISSPACE(c)) { goto quotation; } switch (lex_state) { case EXPR_FNAME: case EXPR_DOT: lex_state = EXPR_ARG; break; default: lex_state = EXPR_BEG; break; } pushback(c); return '%'; case '$': last_state = lex_state; lex_state = EXPR_END; newtok(); c = nextc(); switch (c) { case '_': /* $_: last read line string */ c = nextc(); if (is_identchar(c)) { tokadd('$'); tokadd('_'); break; } pushback(c); c = '_'; /* fall through */ case '~': /* $~: match-data */ local_cnt(c); /* fall through */ case '*': /* $*: argv */ case '$': /* $$: pid */ case '?': /* $?: last status */ case '!': /* $!: error string */ case '@': /* $@: error position */ case '/': /* $/: input record separator */ case '\\': /* $\: output record separator */ case ';': /* $;: field separator */ case ',': /* $,: output field separator */ case '.': /* $.: last read line number */ case '=': /* $=: ignorecase */ case ':': /* $:: load path */ case '<': /* $<: reading filename */ case '>': /* $>: default output handle */ case '\"': /* $": already loaded files */ tokadd('$'); tokadd(c); tokfix(); yylval.id = rb_intern(tok()); return tGVAR; case '-': tokadd('$'); tokadd(c); c = nextc(); if (is_identchar(c)) { tokadd(c); } else { pushback(c); } gvar: tokfix(); yylval.id = rb_intern(tok()); /* xxx shouldn't check if valid option variable */ return tGVAR; case '&': /* $&: last match */ case '`': /* $`: string before last match */ case '\'': /* $': string after last match */ case '+': /* $+: string matches last paren. */ if (last_state == EXPR_FNAME) { tokadd('$'); tokadd(c); goto gvar; } yylval.node = NEW_BACK_REF(c); return tBACK_REF; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': tokadd('$'); do { tokadd(c); c = nextc(); } while (ISDIGIT(c)); pushback(c); if (last_state == EXPR_FNAME) goto gvar; tokfix(); yylval.node = NEW_NTH_REF(atoi(tok()+1)); return tNTH_REF; default: if (!is_identchar(c)) { pushback(c); return '$'; } case '0': tokadd('$'); } break; case '@': c = nextc(); newtok(); tokadd('@'); if (c == '@') { tokadd('@'); c = nextc(); } if (ISDIGIT(c)) { if (tokidx == 1) { rb_compile_error("`@%c' is not allowed as an instance variable name", c); } else { rb_compile_error("`@@%c' is not allowed as a class variable name", c); } return 0; } if (!is_identchar(c)) { pushback(c); return '@'; } break; case '_': if (was_bol() && whole_match_p("__END__", 7, 0)) { ruby__end__seen = 1; lex_lastline = 0; return -1; } newtok(); break; default: if (!is_identchar(c)) { rb_compile_error("Invalid char `\\%03o' in expression", c); goto retry; } newtok(); break; } do { tokadd(c); if (ismbchar(c)) { int i, len = mbclen(c)-1; for (i = 0; i < len; i++) { c = nextc(); tokadd(c); } } c = nextc(); } while (is_identchar(c)); if ((c == '!' || c == '?') && is_identchar(tok()[0]) && !peek('=')) { tokadd(c); } else { pushback(c); } tokfix(); { int result = 0; last_state = lex_state; switch (tok()[0]) { case '$': lex_state = EXPR_END; result = tGVAR; break; case '@': lex_state = EXPR_END; if (tok()[1] == '@') result = tCVAR; else result = tIVAR; break; default: if (toklast() == '!' || toklast() == '?') { result = tFID; } else { if (lex_state == EXPR_FNAME) { if ((c = nextc()) == '=' && !peek('~') && !peek('>') && (!peek('=') || (lex_p + 1 < lex_pend && lex_p[1] == '>'))) { result = tIDENTIFIER; tokadd(c); tokfix(); } else { pushback(c); } } if (result == 0 && ISUPPER(tok()[0])) { result = tCONSTANT; } else { result = tIDENTIFIER; } } if (lex_state != EXPR_DOT) { struct kwtable *kw; /* See if it is a reserved word. */ kw = rb_reserved_word(tok(), toklen()); if (kw) { enum lex_state state = lex_state; lex_state = kw->state; if (state == EXPR_FNAME) { yylval.id = rb_intern(kw->name); return kw->id[0]; } if (kw->id[0] == kDO) { if (COND_P()) return kDO_COND; if (CMDARG_P() && state != EXPR_CMDARG) return kDO_BLOCK; if (state == EXPR_ENDARG) return kDO_BLOCK; return kDO; } if (state == EXPR_BEG) return kw->id[0]; else { if (kw->id[0] != kw->id[1]) lex_state = EXPR_BEG; return kw->id[1]; } } } if (lex_state == EXPR_BEG || lex_state == EXPR_MID || lex_state == EXPR_DOT || lex_state == EXPR_ARG || lex_state == EXPR_CLASS || lex_state == EXPR_CMDARG) { if (cmd_state) { lex_state = EXPR_CMDARG; } else { lex_state = EXPR_ARG; } } else { lex_state = EXPR_END; } } yylval.id = rb_intern(tok()); if (is_local_id(yylval.id) && last_state != EXPR_DOT && ((dyna_in_block() && rb_dvar_defined(yylval.id)) || local_id(yylval.id))) { lex_state = EXPR_END; } return result; } } NODE* rb_node_newnode(type, a0, a1, a2) enum node_type type; VALUE a0, a1, a2; { NODE *n = (NODE*)rb_newobj(); n->flags |= T_NODE; nd_set_type(n, type); nd_set_line(n, ruby_sourceline); n->nd_file = ruby_sourcefile; n->u1.value = a0; n->u2.value = a1; n->u3.value = a2; return n; } static enum node_type nodetype(node) /* for debug */ NODE *node; { return (enum node_type)nd_type(node); } static int nodeline(node) NODE *node; { return nd_line(node); } static NODE* newline_node(node) NODE *node; { NODE *nl = 0; if (node) { int line; if (nd_type(node) == NODE_NEWLINE) return node; line = nd_line(node); node = remove_begin(node); nl = NEW_NEWLINE(node); nd_set_line(nl, line); nl->nd_nth = line; } return nl; } static void fixpos(node, orig) NODE *node, *orig; { if (!node) return; if (!orig) return; if (orig == (NODE*)1) return; node->nd_file = orig->nd_file; nd_set_line(node, nd_line(orig)); } static void parser_warning(node, mesg) NODE *node; const char *mesg; { int line = ruby_sourceline; ruby_sourceline = nd_line(node); rb_warning("%s", mesg); ruby_sourceline = line; } static void parser_warn(node, mesg) NODE *node; const char *mesg; { int line = ruby_sourceline; ruby_sourceline = nd_line(node); rb_warn("%s", mesg); ruby_sourceline = line; } static NODE* block_append(head, tail) NODE *head, *tail; { NODE *end, *h = head; if (tail == 0) return head; again: if (h == 0) return tail; switch (nd_type(h)) { case NODE_NEWLINE: h = h->nd_next; goto again; case NODE_LIT: case NODE_STR: parser_warning(h, "unused literal ignored"); return tail; default: h = end = NEW_BLOCK(head); end->nd_end = end; fixpos(end, head); head = end; break; case NODE_BLOCK: end = h->nd_end; break; } if (RTEST(ruby_verbose)) { NODE *nd = end->nd_head; newline: switch (nd_type(nd)) { case NODE_RETURN: case NODE_BREAK: case NODE_NEXT: case NODE_REDO: case NODE_RETRY: parser_warning(nd, "statement not reached"); break; case NODE_NEWLINE: nd = nd->nd_next; goto newline; default: break; } } if (nd_type(tail) != NODE_BLOCK) { tail = NEW_BLOCK(tail); tail->nd_end = tail; } end->nd_next = tail; h->nd_end = tail->nd_end; return head; } /* append item to the list */ static NODE* list_append(list, item) NODE *list, *item; { NODE *last; if (list == 0) return NEW_LIST(item); if (list->nd_next) { last = list->nd_next->nd_end; } else { last = list; } list->nd_alen += 1; last->nd_next = NEW_LIST(item); list->nd_next->nd_end = last->nd_next; return list; } /* concat two lists */ static NODE* list_concat(head, tail) NODE *head, *tail; { NODE *last; if (head->nd_next) { last = head->nd_next->nd_end; } else { last = head; } head->nd_alen += tail->nd_alen; last->nd_next = tail; if (tail->nd_next) { head->nd_next->nd_end = tail->nd_next->nd_end; } else { head->nd_next->nd_end = tail; } return head; } /* concat two string literals */ static NODE * literal_concat(head, tail) NODE *head, *tail; { enum node_type htype; if (!head) return tail; if (!tail) return head; htype = nd_type(head); if (htype == NODE_EVSTR) { NODE *node = NEW_DSTR(rb_str_new(0, 0)); head = list_append(node, head); } switch (nd_type(tail)) { case NODE_STR: if (htype == NODE_STR) { rb_str_concat(head->nd_lit, tail->nd_lit); rb_gc_force_recycle((VALUE)tail); } else { list_append(head, tail); } break; case NODE_DSTR: if (htype == NODE_STR) { rb_str_concat(head->nd_lit, tail->nd_lit); tail->nd_lit = head->nd_lit; rb_gc_force_recycle((VALUE)head); head = tail; } else { nd_set_type(tail, NODE_ARRAY); tail->nd_head = NEW_STR(tail->nd_lit); list_concat(head, tail); } break; case NODE_EVSTR: if (htype == NODE_STR) { nd_set_type(head, NODE_DSTR); head->nd_alen = 1; } list_append(head, tail); break; } return head; } static NODE * evstr2dstr(node) NODE *node; { if (nd_type(node) == NODE_EVSTR) { node = list_append(NEW_DSTR(rb_str_new(0, 0)), node); } return node; } static NODE * new_evstr(node) NODE *node; { NODE *head = node; again: if (node) { switch (nd_type(node)) { case NODE_STR: case NODE_DSTR: case NODE_EVSTR: return node; case NODE_NEWLINE: node = node->nd_next; goto again; } } return NEW_EVSTR(head); } static NODE * call_op(recv, id, narg, arg1) NODE *recv; ID id; int narg; NODE *arg1; { value_expr(recv); if (narg == 1) { value_expr(arg1); arg1 = NEW_LIST(arg1); } else { arg1 = 0; } return NEW_CALL(recv, id, arg1); } static NODE* match_gen(node1, node2) NODE *node1; NODE *node2; { local_cnt('~'); value_expr(node1); value_expr(node2); if (node1) { switch (nd_type(node1)) { case NODE_DREGX: case NODE_DREGX_ONCE: return NEW_MATCH2(node1, node2); case NODE_LIT: if (TYPE(node1->nd_lit) == T_REGEXP) { return NEW_MATCH2(node1, node2); } } } if (node2) { switch (nd_type(node2)) { case NODE_DREGX: case NODE_DREGX_ONCE: return NEW_MATCH3(node2, node1); case NODE_LIT: if (TYPE(node2->nd_lit) == T_REGEXP) { return NEW_MATCH3(node2, node1); } } } return NEW_CALL(node1, tMATCH, NEW_LIST(node2)); } static NODE* gettable(id) ID id; { if (id == kSELF) { return NEW_SELF(); } else if (id == kNIL) { return NEW_NIL(); } else if (id == kTRUE) { return NEW_TRUE(); } else if (id == kFALSE) { return NEW_FALSE(); } else if (id == k__FILE__) { return NEW_STR(rb_str_new2(ruby_sourcefile)); } else if (id == k__LINE__) { return NEW_LIT(INT2FIX(ruby_sourceline)); } else if (is_local_id(id)) { if (dyna_in_block() && rb_dvar_defined(id)) return NEW_DVAR(id); if (local_id(id)) return NEW_LVAR(id); /* method call without arguments */ #if 0 /* Rite will warn this */ rb_warn("ambiguous identifier; %s() or self.%s is better for method call", rb_id2name(id), rb_id2name(id)); #endif return NEW_VCALL(id); } else if (is_global_id(id)) { return NEW_GVAR(id); } else if (is_instance_id(id)) { return NEW_IVAR(id); } else if (is_const_id(id)) { return NEW_CONST(id); } else if (is_class_id(id)) { return NEW_CVAR(id); } rb_compile_error("identifier %s is not valid", rb_id2name(id)); return 0; } static VALUE dyna_var_lookup _((ID id)); static NODE* assignable(id, val) ID id; NODE *val; { value_expr(val); if (id == kSELF) { yyerror("Can't change the value of self"); } else if (id == kNIL) { yyerror("Can't assign to nil"); } else if (id == kTRUE) { yyerror("Can't assign to true"); } else if (id == kFALSE) { yyerror("Can't assign to false"); } else if (id == k__FILE__) { yyerror("Can't assign to __FILE__"); } else if (id == k__LINE__) { yyerror("Can't assign to __LINE__"); } else if (is_local_id(id)) { if (rb_dvar_curr(id)) { return NEW_DASGN_CURR(id, val); } else if (dyna_var_lookup(id)) { return NEW_DASGN(id, val); } else if (local_id(id) || !dyna_in_block()) { return NEW_LASGN(id, val); } else{ rb_dvar_push(id, Qnil); return NEW_DASGN_CURR(id, val); } } else if (is_global_id(id)) { return NEW_GASGN(id, val); } else if (is_instance_id(id)) { return NEW_IASGN(id, val); } else if (is_const_id(id)) { if (in_def || in_single) yyerror("dynamic constant assignment"); return NEW_CDECL(id, val, 0); } else if (is_class_id(id)) { if (in_def || in_single) return NEW_CVASGN(id, val); return NEW_CVDECL(id, val); } else { rb_compile_error("identifier %s is not valid", rb_id2name(id)); } return 0; } static NODE * aryset(recv, idx) NODE *recv, *idx; { if (recv && nd_type(recv) == NODE_SELF) recv = (NODE *)1; else value_expr(recv); return NEW_ATTRASGN(recv, tASET, idx); } ID rb_id_attrset(id) ID id; { id &= ~ID_SCOPE_MASK; id |= ID_ATTRSET; return id; } static NODE * attrset(recv, id) NODE *recv; ID id; { if (recv && nd_type(recv) == NODE_SELF) recv = (NODE *)1; else value_expr(recv); return NEW_ATTRASGN(recv, rb_id_attrset(id), 0); } static void rb_backref_error(node) NODE *node; { switch (nd_type(node)) { case NODE_NTH_REF: rb_compile_error("Can't set variable $%d", node->nd_nth); break; case NODE_BACK_REF: rb_compile_error("Can't set variable $%c", (int)node->nd_nth); break; } } static NODE * arg_concat(node1, node2) NODE *node1; NODE *node2; { if (!node2) return node1; return NEW_ARGSCAT(node1, node2); } static NODE * arg_add(node1, node2) NODE *node1; NODE *node2; { if (!node1) return NEW_LIST(node2); if (nd_type(node1) == NODE_ARRAY) { return list_append(node1, node2); } else { return NEW_ARGSPUSH(node1, node2); } } static NODE* node_assign(lhs, rhs) NODE *lhs, *rhs; { if (!lhs) return 0; value_expr(rhs); switch (nd_type(lhs)) { case NODE_GASGN: case NODE_IASGN: case NODE_LASGN: case NODE_DASGN: case NODE_DASGN_CURR: case NODE_MASGN: case NODE_CDECL: case NODE_CVDECL: case NODE_CVASGN: lhs->nd_value = rhs; break; case NODE_ATTRASGN: case NODE_CALL: lhs->nd_args = arg_add(lhs->nd_args, rhs); break; default: /* should not happen */ break; } return lhs; } static int value_expr0(node) NODE *node; { int cond = 0; while (node) { switch (nd_type(node)) { case NODE_DEFN: case NODE_DEFS: parser_warning(node, "void value expression"); return Qfalse; case NODE_RETURN: case NODE_BREAK: case NODE_NEXT: case NODE_REDO: case NODE_RETRY: if (!cond) yyerror("void value expression"); /* or "control never reach"? */ return Qfalse; case NODE_BLOCK: while (node->nd_next) { node = node->nd_next; } node = node->nd_head; break; case NODE_BEGIN: node = node->nd_body; break; case NODE_IF: if (!value_expr(node->nd_body)) return Qfalse; node = node->nd_else; break; case NODE_AND: case NODE_OR: cond = 1; node = node->nd_2nd; break; case NODE_NEWLINE: node = node->nd_next; break; default: return Qtrue; } } return Qtrue; } static void void_expr0(node) NODE *node; { char *useless = 0; if (!RTEST(ruby_verbose)) return; again: if (!node) return; switch (nd_type(node)) { case NODE_NEWLINE: node = node->nd_next; goto again; case NODE_CALL: switch (node->nd_mid) { case '+': case '-': case '*': case '/': case '%': case tPOW: case tUPLUS: case tUMINUS: case '|': case '^': case '&': case tCMP: case '>': case tGEQ: case '<': case tLEQ: case tEQ: case tNEQ: useless = rb_id2name(node->nd_mid); break; } break; case NODE_LVAR: case NODE_DVAR: case NODE_GVAR: case NODE_IVAR: case NODE_CVAR: case NODE_NTH_REF: case NODE_BACK_REF: useless = "a variable"; break; case NODE_CONST: case NODE_CREF: useless = "a constant"; break; case NODE_LIT: case NODE_STR: case NODE_DSTR: case NODE_DREGX: case NODE_DREGX_ONCE: useless = "a literal"; break; case NODE_COLON2: case NODE_COLON3: useless = "::"; break; case NODE_DOT2: useless = ".."; break; case NODE_DOT3: useless = "..."; break; case NODE_SELF: useless = "self"; break; case NODE_NIL: useless = "nil"; break; case NODE_TRUE: useless = "true"; break; case NODE_FALSE: useless = "false"; break; case NODE_DEFINED: useless = "defined?"; break; } if (useless) { int line = ruby_sourceline; ruby_sourceline = nd_line(node); rb_warn("useless use of %s in void context", useless); ruby_sourceline = line; } } static void void_stmts(node) NODE *node; { if (!RTEST(ruby_verbose)) return; if (!node) return; if (nd_type(node) != NODE_BLOCK) return; for (;;) { if (!node->nd_next) return; void_expr0(node->nd_head); node = node->nd_next; } } static NODE * remove_begin(node) NODE *node; { NODE **n = &node; while (*n) { switch (nd_type(*n)) { case NODE_NEWLINE: n = &(*n)->nd_next; continue; case NODE_BEGIN: *n = (*n)->nd_body; default: return node; } } return node; } static int assign_in_cond(node) NODE *node; { switch (nd_type(node)) { case NODE_MASGN: yyerror("multiple assignment in conditional"); return 1; case NODE_LASGN: case NODE_DASGN: case NODE_GASGN: case NODE_IASGN: break; case NODE_NEWLINE: default: return 0; } switch (nd_type(node->nd_value)) { case NODE_LIT: case NODE_STR: case NODE_NIL: case NODE_TRUE: case NODE_FALSE: /* reports always */ parser_warn(node->nd_value, "found = in conditional, should be =="); return 1; case NODE_DSTR: case NODE_XSTR: case NODE_DXSTR: case NODE_EVSTR: case NODE_DREGX: default: break; } #if 0 if (assign_in_cond(node->nd_value) == 0) { parser_warning(node->nd_value, "assignment in condition"); } #endif return 1; } static int e_option_supplied() { if (strcmp(ruby_sourcefile, "-e") == 0) return Qtrue; return Qfalse; } static void warn_unless_e_option(node, str) NODE *node; const char *str; { if (!e_option_supplied()) parser_warn(node, str); } static void warning_unless_e_option(node, str) NODE *node; const char *str; { if (!e_option_supplied()) parser_warning(node, str); } static void fixup_nodes(rootnode) NODE **rootnode; { NODE *node, *next, *head; for (node = *rootnode; node; node = next) { enum node_type type; VALUE val; next = node->nd_next; head = node->nd_head; rb_gc_force_recycle((VALUE)node); *rootnode = next; switch (type = nd_type(head)) { case NODE_DOT2: case NODE_DOT3: val = rb_range_new(head->nd_beg->nd_lit, head->nd_end->nd_lit, type == NODE_DOT3 ? Qtrue : Qfalse); rb_gc_force_recycle((VALUE)head->nd_beg); rb_gc_force_recycle((VALUE)head->nd_end); nd_set_type(head, NODE_LIT); head->nd_lit = val; break; default: break; } } } static NODE *cond0(); static NODE* range_op(node) NODE *node; { enum node_type type; if (node == 0) return 0; type = nd_type(node); if (type == NODE_NEWLINE) { node = node->nd_next; type = nd_type(node); } value_expr(node); if (type == NODE_LIT && FIXNUM_P(node->nd_lit)) { warn_unless_e_option(node, "integer literal in conditional range"); return call_op(node,tEQ,1,NEW_GVAR(rb_intern("$."))); } return cond0(node); } static int literal_node(node) NODE *node; { if (!node) return 1; /* same as NODE_NIL */ switch (nd_type(node)) { case NODE_LIT: case NODE_STR: case NODE_DSTR: case NODE_EVSTR: case NODE_DREGX: case NODE_DREGX_ONCE: case NODE_DSYM: return 2; case NODE_TRUE: case NODE_FALSE: case NODE_NIL: return 1; } return 0; } static NODE* cond0(node) NODE *node; { if (node == 0) return 0; assign_in_cond(node); switch (nd_type(node)) { case NODE_DSTR: case NODE_EVSTR: case NODE_STR: rb_warn("string literal in condition"); break; case NODE_DREGX: case NODE_DREGX_ONCE: warning_unless_e_option(node, "regex literal in condition"); local_cnt('_'); local_cnt('~'); return NEW_MATCH2(node, NEW_GVAR(rb_intern("$_"))); case NODE_AND: case NODE_OR: node->nd_1st = cond0(node->nd_1st); node->nd_2nd = cond0(node->nd_2nd); break; case NODE_DOT2: case NODE_DOT3: node->nd_beg = range_op(node->nd_beg); node->nd_end = range_op(node->nd_end); if (nd_type(node) == NODE_DOT2) nd_set_type(node,NODE_FLIP2); else if (nd_type(node) == NODE_DOT3) nd_set_type(node, NODE_FLIP3); node->nd_cnt = local_append(internal_id()); if (!e_option_supplied()) { int b = literal_node(node->nd_beg); int e = literal_node(node->nd_end); if ((b == 1 && e == 1) || (b + e >= 2 && RTEST(ruby_verbose))) { parser_warn(node, "range literal in condition"); } } break; case NODE_DSYM: parser_warning(node, "literal in condition"); break; case NODE_LIT: if (TYPE(node->nd_lit) == T_REGEXP) { warn_unless_e_option(node, "regex literal in condition"); nd_set_type(node, NODE_MATCH); local_cnt('_'); local_cnt('~'); } else { parser_warning(node, "literal in condition"); } default: break; } return node; } static NODE* cond(node) NODE *node; { if (node == 0) return 0; value_expr(node); if (nd_type(node) == NODE_NEWLINE){ node->nd_next = cond0(node->nd_next); return node; } return cond0(node); } static NODE* logop(type, left, right) enum node_type type; NODE *left, *right; { value_expr(left); if (left && nd_type(left) == type) { NODE *node = left, *second; while ((second = node->nd_2nd) != 0 && nd_type(second) == type) { node = second; } node->nd_2nd = NEW_NODE(type, second, right, 0); return left; } return NEW_NODE(type, left, right, 0); } static int cond_negative(nodep) NODE **nodep; { NODE *c = *nodep; if (!c) return 0; switch (nd_type(c)) { case NODE_NOT: *nodep = c->nd_body; return 1; case NODE_NEWLINE: if (c->nd_next && nd_type(c->nd_next) == NODE_NOT) { c->nd_next = c->nd_next->nd_body; return 1; } } return 0; } static void no_blockarg(node) NODE *node; { if (node && nd_type(node) == NODE_BLOCK_PASS) { rb_compile_error("block argument should not be given"); } } static NODE * ret_args(node) NODE *node; { if (node) { no_blockarg(node); if (nd_type(node) == NODE_ARRAY && node->nd_next == 0) { node = node->nd_head; } if (node && nd_type(node) == NODE_SPLAT) { node = NEW_SVALUE(node); } } return node; } static NODE * new_yield(node) NODE *node; { long state = Qtrue; if (node) { no_blockarg(node); if (nd_type(node) == NODE_ARRAY && node->nd_next == 0) { node = node->nd_head; state = Qfalse; } if (node && nd_type(node) == NODE_SPLAT) { state = Qtrue; } } else { state = Qfalse; } return NEW_YIELD(node, state); } static NODE* negate_lit(node) NODE *node; { switch (TYPE(node->nd_lit)) { case T_FIXNUM: node->nd_lit = LONG2FIX(-FIX2LONG(node->nd_lit)); break; case T_BIGNUM: node->nd_lit = rb_funcall(node->nd_lit,tUMINUS,0,0); break; case T_FLOAT: RFLOAT(node->nd_lit)->value = -RFLOAT(node->nd_lit)->value; break; default: break; } return node; } static NODE * arg_blk_pass(node1, node2) NODE *node1; NODE *node2; { if (node2) { node2->nd_head = node1; return node2; } return node1; } static NODE* arg_prepend(node1, node2) NODE *node1, *node2; { switch (nd_type(node2)) { case NODE_ARRAY: return list_concat(NEW_LIST(node1), node2); case NODE_SPLAT: return arg_concat(node1, node2->nd_head); case NODE_BLOCK_PASS: node2->nd_body = arg_prepend(node1, node2->nd_body); return node2; default: rb_bug("unknown nodetype(%d) for arg_prepend", nd_type(node2)); } return 0; /* not reached */ } static NODE* new_call(r,m,a) NODE *r; ID m; NODE *a; { if (a && nd_type(a) == NODE_BLOCK_PASS) { a->nd_iter = NEW_CALL(r,m,a->nd_head); return a; } return NEW_CALL(r,m,a); } static NODE* new_fcall(m,a) ID m; NODE *a; { if (a && nd_type(a) == NODE_BLOCK_PASS) { a->nd_iter = NEW_FCALL(m,a->nd_head); return a; } return NEW_FCALL(m,a); } static NODE* new_super(a) NODE *a; { if (a && nd_type(a) == NODE_BLOCK_PASS) { a->nd_iter = NEW_SUPER(a->nd_head); return a; } return NEW_SUPER(a); } static struct local_vars { ID *tbl; int nofree; int cnt; int dlev; struct RVarmap* dyna_vars; struct local_vars *prev; } *lvtbl; static void local_push(top) int top; { struct local_vars *local; local = ALLOC(struct local_vars); local->prev = lvtbl; local->nofree = 0; local->cnt = 0; local->tbl = 0; local->dlev = 0; local->dyna_vars = ruby_dyna_vars; lvtbl = local; if (!top) { /* preserve reference for GC, but link should be cut. */ rb_dvar_push(0, (VALUE)ruby_dyna_vars); ruby_dyna_vars->next = 0; } } static void local_pop() { struct local_vars *local = lvtbl->prev; if (lvtbl->tbl) { if (!lvtbl->nofree) xfree(lvtbl->tbl); else lvtbl->tbl[0] = lvtbl->cnt; } ruby_dyna_vars = lvtbl->dyna_vars; xfree(lvtbl); lvtbl = local; } static ID* local_tbl() { lvtbl->nofree = 1; return lvtbl->tbl; } static int local_append(id) ID id; { if (lvtbl->tbl == 0) { lvtbl->tbl = ALLOC_N(ID, 4); lvtbl->tbl[0] = 0; lvtbl->tbl[1] = '_'; lvtbl->tbl[2] = '~'; lvtbl->cnt = 2; if (id == '_') return 0; if (id == '~') return 1; } else { REALLOC_N(lvtbl->tbl, ID, lvtbl->cnt+2); } lvtbl->tbl[lvtbl->cnt+1] = id; return lvtbl->cnt++; } static int local_cnt(id) ID id; { int cnt, max; if (id == 0) return lvtbl->cnt; for (cnt=1, max=lvtbl->cnt+1; cnttbl[cnt] == id) return cnt-1; } return local_append(id); } static int local_id(id) ID id; { int i, max; if (lvtbl == 0) return Qfalse; for (i=3, max=lvtbl->cnt+1; itbl[i] == id) return Qtrue; } return Qfalse; } static void top_local_init() { local_push(1); lvtbl->cnt = ruby_scope->local_tbl?ruby_scope->local_tbl[0]:0; if (lvtbl->cnt > 0) { lvtbl->tbl = ALLOC_N(ID, lvtbl->cnt+3); MEMCPY(lvtbl->tbl, ruby_scope->local_tbl, ID, lvtbl->cnt+1); } else { lvtbl->tbl = 0; } if (ruby_dyna_vars) lvtbl->dlev = 1; else lvtbl->dlev = 0; } static void top_local_setup() { int len = lvtbl->cnt; int i; if (len > 0) { i = ruby_scope->local_tbl?ruby_scope->local_tbl[0]:0; if (i < len) { if (i == 0 || (ruby_scope->flags & SCOPE_MALLOC) == 0) { VALUE *vars = ALLOC_N(VALUE, len+1); if (ruby_scope->local_vars) { *vars++ = ruby_scope->local_vars[-1]; MEMCPY(vars, ruby_scope->local_vars, VALUE, i); rb_mem_clear(vars+i, len-i); } else { *vars++ = 0; rb_mem_clear(vars, len); } ruby_scope->local_vars = vars; ruby_scope->flags |= SCOPE_MALLOC; } else { VALUE *vars = ruby_scope->local_vars-1; REALLOC_N(vars, VALUE, len+1); ruby_scope->local_vars = vars+1; rb_mem_clear(ruby_scope->local_vars+i, len-i); } if (ruby_scope->local_tbl && ruby_scope->local_vars[-1] == 0) { if (!(ruby_scope->flags & SCOPE_CLONE)) xfree(ruby_scope->local_tbl); } ruby_scope->local_vars[-1] = 0; /* no reference needed */ ruby_scope->local_tbl = local_tbl(); } } local_pop(); } #define DVAR_USED FL_USER6 static VALUE dyna_var_lookup(id) ID id; { struct RVarmap *vars = ruby_dyna_vars; while (vars) { if (vars->id == id) { FL_SET(vars, DVAR_USED); return Qtrue; } vars = vars->next; } return Qfalse; } static struct RVarmap* dyna_push() { struct RVarmap* vars = ruby_dyna_vars; rb_dvar_push(0, 0); lvtbl->dlev++; return vars; } static void dyna_pop(vars) struct RVarmap* vars; { lvtbl->dlev--; ruby_dyna_vars = vars; } static int dyna_in_block() { return (lvtbl->dlev > 0); } static NODE * dyna_init(node, pre) NODE *node; struct RVarmap *pre; { struct RVarmap *post = ruby_dyna_vars; NODE *var; if (!node || !post || pre == post) return node; for (var = 0; post != pre && post->id; post = post->next) { if (FL_TEST(post, DVAR_USED)) { var = NEW_DASGN_CURR(post->id, var); } } return block_append(var, node); } int ruby_parser_stack_on_heap() { #if defined(YYMALLOC) return Qfalse; #else return Qtrue; #endif } void rb_gc_mark_parser() { #if defined YYMALLOC rb_gc_mark((VALUE)parser_heap); #elif defined yystacksize if (yyvsp) rb_gc_mark_locations((VALUE *)yyvs, (VALUE *)yyvsp); #endif if (!ruby_in_compile) return; rb_gc_mark_maybe((VALUE)yylval.node); rb_gc_mark(ruby_debug_lines); rb_gc_mark(lex_lastline); rb_gc_mark(lex_input); rb_gc_mark((VALUE)lex_strterm); rb_gc_mark((VALUE)deferred_nodes); } void rb_parser_append_print() { ruby_eval_tree = block_append(ruby_eval_tree, NEW_FCALL(rb_intern("print"), NEW_ARRAY(NEW_GVAR(rb_intern("$_"))))); } void rb_parser_while_loop(chop, split) int chop, split; { if (split) { ruby_eval_tree = block_append(NEW_GASGN(rb_intern("$F"), NEW_CALL(NEW_GVAR(rb_intern("$_")), rb_intern("split"), 0)), ruby_eval_tree); } if (chop) { ruby_eval_tree = block_append(NEW_CALL(NEW_GVAR(rb_intern("$_")), rb_intern("chop!"), 0), ruby_eval_tree); } ruby_eval_tree = NEW_OPT_N(ruby_eval_tree); } static struct { ID token; char *name; } op_tbl[] = { {tDOT2, ".."}, {tDOT3, "..."}, {'+', "+"}, {'-', "-"}, {'+', "+(binary)"}, {'-', "-(binary)"}, {'*', "*"}, {'/', "/"}, {'%', "%"}, {tPOW, "**"}, {tUPLUS, "+@"}, {tUMINUS, "-@"}, {tUPLUS, "+(unary)"}, {tUMINUS, "-(unary)"}, {'|', "|"}, {'^', "^"}, {'&', "&"}, {tCMP, "<=>"}, {'>', ">"}, {tGEQ, ">="}, {'<', "<"}, {tLEQ, "<="}, {tEQ, "=="}, {tEQQ, "==="}, {tNEQ, "!="}, {tMATCH, "=~"}, {tNMATCH, "!~"}, {'!', "!"}, {'~', "~"}, {'!', "!(unary)"}, {'~', "~(unary)"}, {'!', "!@"}, {'~', "~@"}, {tAREF, "[]"}, {tASET, "[]="}, {tLSHFT, "<<"}, {tRSHFT, ">>"}, {tCOLON2, "::"}, {'`', "`"}, {0, 0} }; static st_table *sym_tbl; static st_table *sym_rev_tbl; void Init_sym() { sym_tbl = st_init_strtable_with_size(200); sym_rev_tbl = st_init_numtable_with_size(200); } static ID last_id = tLAST_TOKEN; static ID internal_id() { return ID_INTERNAL | (++last_id << ID_SCOPE_SHIFT); } static int is_special_global_name(m) const char *m; { switch (*m) { case '~': case '*': case '$': case '?': case '!': case '@': case '/': case '\\': case ';': case ',': case '.': case '=': case ':': case '<': case '>': case '\"': case '&': case '`': case '\'': case '+': case '0': ++m; break; case '-': ++m; if (is_identchar(*m)) m += mbclen(*m); break; default: if (!ISDIGIT(*m)) return 0; do ++m; while (ISDIGIT(*m)); } return !*m; } int rb_symname_p(name) const char *name; { const char *m = name; int localid = Qfalse; if (!m) return Qfalse; switch (*m) { case '\0': return Qfalse; case '$': if (is_special_global_name(++m)) return Qtrue; goto id; case '@': if (*++m == '@') ++m; goto id; case '<': switch (*++m) { case '<': ++m; break; case '=': if (*++m == '>') ++m; break; default: break; } break; case '>': switch (*++m) { case '>': case '=': ++m; break; } break; case '=': switch (*++m) { case '~': ++m; break; case '=': if (*++m == '=') ++m; break; default: return Qfalse; } break; case '*': if (*++m == '*') ++m; break; case '+': case '-': if (*++m == '@') ++m; break; case '|': case '^': case '&': case '/': case '%': case '~': case '`': ++m; break; case '[': if (*++m != ']') return Qfalse; if (*++m == '=') ++m; break; default: localid = !ISUPPER(*m); id: if (*m != '_' && !ISALPHA(*m) && !ismbchar(*m)) return Qfalse; while (is_identchar(*m)) m += mbclen(*m); if (localid) { switch (*m) { case '!': case '?': case '=': ++m; } } break; } return *m ? Qfalse : Qtrue; } int rb_sym_interned_p(str) VALUE str; { ID id; if (st_lookup(sym_tbl, (st_data_t)RSTRING(str)->ptr, (st_data_t *)&id)) return Qtrue; return Qfalse; } ID rb_intern(name) const char *name; { const char *m = name; ID id; int last; if (st_lookup(sym_tbl, (st_data_t)name, (st_data_t *)&id)) return id; last = strlen(name)-1; id = 0; switch (*name) { case '$': id |= ID_GLOBAL; if (is_special_global_name(++m)) goto new_id; break; case '@': if (name[1] == '@') { m++; id |= ID_CLASS; } else { id |= ID_INSTANCE; } m++; break; default: if (name[0] != '_' && ISASCII(name[0]) && !ISALNUM(name[0])) { /* operators */ int i; for (i=0; op_tbl[i].token; i++) { if (*op_tbl[i].name == *name && strcmp(op_tbl[i].name, name) == 0) { id = op_tbl[i].token; goto id_regist; } } } if (name[last] == '=') { /* attribute assignment */ char *buf = ALLOCA_N(char,last+1); strncpy(buf, name, last); buf[last] = '\0'; id = rb_intern(buf); if (id > tLAST_TOKEN && !is_attrset_id(id)) { id = rb_id_attrset(id); goto id_regist; } id = ID_ATTRSET; } else if (ISUPPER(name[0])) { id = ID_CONST; } else { id = ID_LOCAL; } break; } if (!ISDIGIT(*m)) { while (m <= name + last && is_identchar(*m)) { m += mbclen(*m); } } if (*m) id = ID_JUNK; new_id: id |= ++last_id << ID_SCOPE_SHIFT; id_regist: name = strdup(name); st_add_direct(sym_tbl, (st_data_t)name, id); st_add_direct(sym_rev_tbl, id, (st_data_t)name); return id; } char * rb_id2name(id) ID id; { char *name; st_data_t data; if (id < tLAST_TOKEN) { int i; for (i=0; op_tbl[i].token; i++) { if (op_tbl[i].token == id) return op_tbl[i].name; } } if (st_lookup(sym_rev_tbl, id, &data)) return (char *)data; if (is_attrset_id(id)) { ID id2 = (id & ~ID_SCOPE_MASK) | ID_LOCAL; again: name = rb_id2name(id2); if (name) { char *buf = ALLOCA_N(char, strlen(name)+2); strcpy(buf, name); strcat(buf, "="); rb_intern(buf); return rb_id2name(id); } if (is_local_id(id2)) { id2 = (id & ~ID_SCOPE_MASK) | ID_CONST; goto again; } } return 0; } static int symbols_i(key, value, ary) char *key; ID value; VALUE ary; { rb_ary_push(ary, ID2SYM(value)); return ST_CONTINUE; } /* * call-seq: * Symbol.all_symbols => array * * Returns an array of all the symbols currently in Ruby's symbol * table. * * Symbol.all_symbols.size #=> 903 * Symbol.all_symbols[1,20] #=> [:floor, :ARGV, :Binding, :symlink, * :chown, :EOFError, :$;, :String, * :LOCK_SH, :"setuid?", :$<, * :default_proc, :compact, :extend, * :Tms, :getwd, :$=, :ThreadGroup, * :wait2, :$>] */ VALUE rb_sym_all_symbols() { VALUE ary = rb_ary_new2(sym_tbl->num_entries); st_foreach(sym_tbl, symbols_i, ary); return ary; } int rb_is_const_id(id) ID id; { if (is_const_id(id)) return Qtrue; return Qfalse; } int rb_is_class_id(id) ID id; { if (is_class_id(id)) return Qtrue; return Qfalse; } int rb_is_instance_id(id) ID id; { if (is_instance_id(id)) return Qtrue; return Qfalse; } int rb_is_local_id(id) ID id; { if (is_local_id(id)) return Qtrue; return Qfalse; } int rb_is_junk_id(id) ID id; { if (is_junk_id(id)) return Qtrue; return Qfalse; } static void special_local_set(c, val) char c; VALUE val; { int cnt; top_local_init(); cnt = local_cnt(c); top_local_setup(); ruby_scope->local_vars[cnt] = val; } VALUE rb_backref_get() { VALUE *var = rb_svar(1); if (var) { return *var; } return Qnil; } void rb_backref_set(val) VALUE val; { VALUE *var = rb_svar(1); if (var) { *var = val; } else { special_local_set('~', val); } } VALUE rb_lastline_get() { VALUE *var = rb_svar(0); if (var) { return *var; } return Qnil; } void rb_lastline_set(val) VALUE val; { VALUE *var = rb_svar(0); if (var) { *var = val; } else { special_local_set('_', val); } } #ifdef YYMALLOC #define HEAPCNT(n, size) ((n) * (size) / sizeof(YYSTYPE)) #define NEWHEAP() rb_node_newnode(NODE_ALLOCA, 0, (VALUE)parser_heap, 0) #define ADD2HEAP(n, c, p) ((parser_heap = (n))->u1.node = (p), \ (n)->u3.cnt = (c), (p)) static void * rb_parser_malloc(size) size_t size; { size_t cnt = HEAPCNT(1, size); NODE *n = NEWHEAP(); void *ptr = xmalloc(size); return ADD2HEAP(n, cnt, ptr); } static void * rb_parser_calloc(nelem, size) size_t nelem, size; { size_t cnt = HEAPCNT(nelem, size); NODE *n = NEWHEAP(); void *ptr = xcalloc(nelem, size); return ADD2HEAP(n, cnt, ptr); } static void * rb_parser_realloc(ptr, size) void *ptr; size_t size; { NODE *n; size_t cnt = HEAPCNT(1, size); if (ptr && (n = parser_heap) != NULL) { do { if (n->u1.node == ptr) { n->u1.node = ptr = xrealloc(ptr, size); if (n->u3.cnt) n->u3.cnt = cnt; return ptr; } } while ((n = n->u2.node) != NULL); } n = NEWHEAP(); ptr = xrealloc(ptr, size); return ADD2HEAP(n, cnt, ptr); } static void rb_parser_free(ptr) void *ptr; { NODE **prev = &parser_heap, *n; while ((n = *prev) != 0) { if (n->u1.node == ptr) { *prev = n->u2.node; rb_gc_force_recycle((VALUE)n); break; } prev = &n->u2.node; } xfree(ptr); } #endif /********************************************************************** prec.c - $Author: shyouhei $ $Date: 2007-02-13 00:01:19 +0100 (Tue, 13 Feb 2007) $ created at: Tue Jan 26 02:40:41 2000 Copyright (C) 1993-2003 Yukihiro Matsumoto **********************************************************************/ #include "ruby.h" VALUE rb_mPrecision; static ID prc_pr, prc_if; /* * call-seq: * num.prec(klass) => a_klass * * Converts _self_ into an instance of _klass_. By default, * +prec+ invokes * * klass.induced_from(num) * * and returns its value. So, if klass.induced_from * doesn't return an instance of _klass_, it will be necessary * to reimplement +prec+. */ static VALUE prec_prec(x, klass) VALUE x, klass; { return rb_funcall(klass, prc_if, 1, x); } /* * call-seq: * num.prec_i => Integer * * Returns an +Integer+ converted from _num_. It is equivalent * to prec(Integer). */ static VALUE prec_prec_i(x) VALUE x; { VALUE klass = rb_cInteger; return rb_funcall(x, prc_pr, 1, klass); } /* * call-seq: * num.prec_f => Float * * Returns a +Float+ converted from _num_. It is equivalent * to prec(Float). */ static VALUE prec_prec_f(x) VALUE x; { VALUE klass = rb_cFloat; return rb_funcall(x, prc_pr, 1, klass); } /* * call-seq: * Mod.induced_from(number) => a_mod * * Creates an instance of mod from. This method is overridden * by concrete +Numeric+ classes, so that (for example) * * Fixnum.induced_from(9.9) #=> 9 * * Note that a use of +prec+ in a redefinition may cause * an infinite loop. */ static VALUE prec_induced_from(module, x) VALUE module, x; { rb_raise(rb_eTypeError, "undefined conversion from %s into %s", rb_obj_classname(x), rb_class2name(module)); return Qnil; /* not reached */ } /* * call_seq: * included * * When the +Precision+ module is mixed-in to a class, this +included+ * method is used to add our default +induced_from+ implementation * to the host class. */ static VALUE prec_included(module, include) VALUE module, include; { switch (TYPE(include)) { case T_CLASS: case T_MODULE: break; default: Check_Type(include, T_CLASS); break; } rb_define_singleton_method(include, "induced_from", prec_induced_from, 1); return module; } /* * Precision is a mixin for concrete numeric classes with * precision. Here, `precision' means the fineness of approximation * of a real number, so, this module should not be included into * anything which is not a subset of Real (so it should not be * included in classes such as +Complex+ or +Matrix+). */ void Init_Precision() { rb_mPrecision = rb_define_module("Precision"); rb_define_singleton_method(rb_mPrecision, "included", prec_included, 1); rb_define_method(rb_mPrecision, "prec", prec_prec, 1); rb_define_method(rb_mPrecision, "prec_i", prec_prec_i, 0); rb_define_method(rb_mPrecision, "prec_f", prec_prec_f, 0); prc_pr = rb_intern("prec"); prc_if = rb_intern("induced_from"); } /********************************************************************** process.c - $Author: shyouhei $ $Date: 2008-06-29 11:33:11 +0200 (Sun, 29 Jun 2008) $ created at: Tue Aug 10 14:30:50 JST 1993 Copyright (C) 1993-2003 Yukihiro Matsumoto Copyright (C) 2000 Network Applied Communication Laboratory, Inc. Copyright (C) 2000 Information-technology Promotion Agency, Japan **********************************************************************/ #include "ruby.h" #include "rubysig.h" #include #include #include #ifdef HAVE_STDLIB_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef __DJGPP__ #include #endif #include #include #ifndef EXIT_SUCCESS #define EXIT_SUCCESS 0 #endif #ifndef EXIT_FAILURE #define EXIT_FAILURE 1 #endif struct timeval rb_time_interval _((VALUE)); #ifdef HAVE_SYS_WAIT_H # include #endif #ifdef HAVE_SYS_RESOURCE_H # include #endif #include "st.h" #ifdef __EMX__ #undef HAVE_GETPGRP #endif #ifdef HAVE_SYS_TIMES_H #include #endif #ifdef HAVE_GRP_H #include #endif #if defined(HAVE_TIMES) || defined(_WIN32) static VALUE S_Tms; #endif #ifndef WIFEXITED #define WIFEXITED(w) (((w) & 0xff) == 0) #endif #ifndef WIFSIGNALED #define WIFSIGNALED(w) (((w) & 0x7f) > 0 && (((w) & 0x7f) < 0x7f)) #endif #ifndef WIFSTOPPED #define WIFSTOPPED(w) (((w) & 0xff) == 0x7f) #endif #ifndef WEXITSTATUS #define WEXITSTATUS(w) (((w) >> 8) & 0xff) #endif #ifndef WTERMSIG #define WTERMSIG(w) ((w) & 0x7f) #endif #ifndef WSTOPSIG #define WSTOPSIG WEXITSTATUS #endif #if defined(__APPLE__) && ( defined(__MACH__) || defined(__DARWIN__) ) && !defined(__MacOS_X__) #define __MacOS_X__ 1 #endif #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__) #define HAVE_44BSD_SETUID 1 #define HAVE_44BSD_SETGID 1 #endif #ifdef __NetBSD__ #undef HAVE_SETRUID #undef HAVE_SETRGID #endif #if defined(__MacOS_X__) || defined(__bsdi__) #define BROKEN_SETREUID 1 #define BROKEN_SETREGID 1 #endif #ifdef BROKEN_SETREUID #define setreuid ruby_setreuid #endif #ifdef BROKEN_SETREGID #define setregid ruby_setregid #endif #if defined(HAVE_44BSD_SETUID) || defined(__MacOS_X__) #if !defined(USE_SETREUID) && !defined(BROKEN_SETREUID) #define OBSOLETE_SETREUID 1 #endif #if !defined(USE_SETREGID) && !defined(BROKEN_SETREGID) #define OBSOLETE_SETREGID 1 #endif #endif #define preserving_errno(stmts) \ do {int saved_errno = errno; stmts; errno = saved_errno;} while (0) /* * call-seq: * Process.pid => fixnum * * Returns the process id of this process. Not available on all * platforms. * * Process.pid #=> 27415 */ static VALUE get_pid() { rb_secure(2); return INT2FIX(getpid()); } /* * call-seq: * Process.ppid => fixnum * * Returns the process id of the parent of this process. Always * returns 0 on NT. Not available on all platforms. * * puts "I am #{Process.pid}" * Process.fork { puts "Dad is #{Process.ppid}" } * * produces: * * I am 27417 * Dad is 27417 */ static VALUE get_ppid() { rb_secure(2); #ifdef _WIN32 return INT2FIX(0); #else return INT2FIX(getppid()); #endif } /********************************************************************* * * Document-class: Process::Status * * Process::Status encapsulates the information on the * status of a running or terminated system process. The built-in * variable $? is either +nil+ or a * Process::Status object. * * fork { exit 99 } #=> 26557 * Process.wait #=> 26557 * $?.class #=> Process::Status * $?.to_i #=> 25344 * $? >> 8 #=> 99 * $?.stopped? #=> false * $?.exited? #=> true * $?.exitstatus #=> 99 * * Posix systems record information on processes using a 16-bit * integer. The lower bits record the process status (stopped, * exited, signaled) and the upper bits possibly contain additional * information (for example the program's return code in the case of * exited processes). Pre Ruby 1.8, these bits were exposed directly * to the Ruby program. Ruby now encapsulates these in a * Process::Status object. To maximize compatibility, * however, these objects retain a bit-oriented interface. In the * descriptions that follow, when we talk about the integer value of * _stat_, we're referring to this 16 bit value. */ static VALUE rb_cProcStatus; VALUE rb_last_status = Qnil; static void last_status_set(status, pid) int status, pid; { rb_last_status = rb_obj_alloc(rb_cProcStatus); rb_iv_set(rb_last_status, "status", INT2FIX(status)); rb_iv_set(rb_last_status, "pid", INT2FIX(pid)); } /* * call-seq: * stat.to_i => fixnum * stat.to_int => fixnum * * Returns the bits in _stat_ as a Fixnum. Poking * around in these bits is platform dependent. * * fork { exit 0xab } #=> 26566 * Process.wait #=> 26566 * sprintf('%04x', $?.to_i) #=> "ab00" */ static VALUE pst_to_i(st) VALUE st; { return rb_iv_get(st, "status"); } /* * call-seq: * stat.to_s => string * * Equivalent to _stat_.to_i.to_s. */ static VALUE pst_to_s(st) VALUE st; { return rb_fix2str(pst_to_i(st), 10); } /* * call-seq: * stat.pid => fixnum * * Returns the process ID that this status object represents. * * fork { exit } #=> 26569 * Process.wait #=> 26569 * $?.pid #=> 26569 */ static VALUE pst_pid(st) VALUE st; { return rb_iv_get(st, "pid"); } /* * call-seq: * stat.inspect => string * * Override the inspection method. */ static VALUE pst_inspect(st) VALUE st; { VALUE pid; int status; VALUE str; char buf[256]; pid = pst_pid(st); status = NUM2INT(st); snprintf(buf, sizeof(buf), "#<%s: pid=%ld", rb_class2name(CLASS_OF(st)), NUM2LONG(pid)); str = rb_str_new2(buf); if (WIFSTOPPED(status)) { int stopsig = WSTOPSIG(status); const char *signame = ruby_signal_name(stopsig); if (signame) { snprintf(buf, sizeof(buf), ",stopped(SIG%s=%d)", signame, stopsig); } else { snprintf(buf, sizeof(buf), ",stopped(%d)", stopsig); } rb_str_cat2(str, buf); } if (WIFSIGNALED(status)) { int termsig = WTERMSIG(status); const char *signame = ruby_signal_name(termsig); if (signame) { snprintf(buf, sizeof(buf), ",signaled(SIG%s=%d)", signame, termsig); } else { snprintf(buf, sizeof(buf), ",signaled(%d)", termsig); } rb_str_cat2(str, buf); } if (WIFEXITED(status)) { snprintf(buf, sizeof(buf), ",exited(%d)", WEXITSTATUS(status)); rb_str_cat2(str, buf); } #ifdef WCOREDUMP if (WCOREDUMP(status)) { rb_str_cat2(str, ",coredumped"); } #endif rb_str_cat2(str, ">"); return str; } /* * call-seq: * stat == other => true or false * * Returns +true+ if the integer value of _stat_ * equals other. */ static VALUE pst_equal(st1, st2) VALUE st1, st2; { if (st1 == st2) return Qtrue; return rb_equal(pst_to_i(st1), st2); } /* * call-seq: * stat & num => fixnum * * Logical AND of the bits in _stat_ with num. * * fork { exit 0x37 } * Process.wait * sprintf('%04x', $?.to_i) #=> "3700" * sprintf('%04x', $? & 0x1e00) #=> "1600" */ static VALUE pst_bitand(st1, st2) VALUE st1, st2; { int status = NUM2INT(st1) & NUM2INT(st2); return INT2NUM(status); } /* * call-seq: * stat >> num => fixnum * * Shift the bits in _stat_ right num places. * * fork { exit 99 } #=> 26563 * Process.wait #=> 26563 * $?.to_i #=> 25344 * $? >> 8 #=> 99 */ static VALUE pst_rshift(st1, st2) VALUE st1, st2; { int status = NUM2INT(st1) >> NUM2INT(st2); return INT2NUM(status); } /* * call-seq: * stat.stopped? => true or false * * Returns +true+ if this process is stopped. This is only * returned if the corresponding wait call had the * WUNTRACED flag set. */ static VALUE pst_wifstopped(st) VALUE st; { int status = NUM2INT(st); if (WIFSTOPPED(status)) return Qtrue; else return Qfalse; } /* * call-seq: * stat.stopsig => fixnum or nil * * Returns the number of the signal that caused _stat_ to stop * (or +nil+ if self is not stopped). */ static VALUE pst_wstopsig(st) VALUE st; { int status = NUM2INT(st); if (WIFSTOPPED(status)) return INT2NUM(WSTOPSIG(status)); return Qnil; } /* * call-seq: * stat.signaled? => true or false * * Returns +true+ if _stat_ terminated because of * an uncaught signal. */ static VALUE pst_wifsignaled(st) VALUE st; { int status = NUM2INT(st); if (WIFSIGNALED(status)) return Qtrue; else return Qfalse; } /* * call-seq: * stat.termsig => fixnum or nil * * Returns the number of the signal that caused _stat_ to * terminate (or +nil+ if self was not terminated by an * uncaught signal). */ static VALUE pst_wtermsig(st) VALUE st; { int status = NUM2INT(st); if (WIFSIGNALED(status)) return INT2NUM(WTERMSIG(status)); return Qnil; } /* * call-seq: * stat.exited? => true or false * * Returns +true+ if _stat_ exited normally (for * example using an exit() call or finishing the * program). */ static VALUE pst_wifexited(st) VALUE st; { int status = NUM2INT(st); if (WIFEXITED(status)) return Qtrue; else return Qfalse; } /* * call-seq: * stat.exitstatus => fixnum or nil * * Returns the least significant eight bits of the return code of * _stat_. Only available if exited? is * +true+. * * fork { } #=> 26572 * Process.wait #=> 26572 * $?.exited? #=> true * $?.exitstatus #=> 0 * * fork { exit 99 } #=> 26573 * Process.wait #=> 26573 * $?.exited? #=> true * $?.exitstatus #=> 99 */ static VALUE pst_wexitstatus(st) VALUE st; { int status = NUM2INT(st); if (WIFEXITED(status)) return INT2NUM(WEXITSTATUS(status)); return Qnil; } /* * call-seq: * stat.success? => true, false or nil * * Returns +true+ if _stat_ is successful, +false+ if not. * Returns +nil+ if exited? is not +true+. */ static VALUE pst_success_p(st) VALUE st; { int status = NUM2INT(st); if (!WIFEXITED(status)) return Qnil; return WEXITSTATUS(status) == EXIT_SUCCESS ? Qtrue : Qfalse; } /* * call-seq: * stat.coredump? => true or false * * Returns +true+ if _stat_ generated a coredump * when it terminated. Not available on all platforms. */ static VALUE pst_wcoredump(st) VALUE st; { #ifdef WCOREDUMP int status = NUM2INT(st); if (WCOREDUMP(status)) return Qtrue; else return Qfalse; #else return Qfalse; #endif } #if !defined(HAVE_WAITPID) && !defined(HAVE_WAIT4) #define NO_WAITPID static st_table *pid_tbl; #endif int rb_waitpid(pid, st, flags) int pid; int *st; int flags; { int result; #ifndef NO_WAITPID int oflags = flags; if (!rb_thread_alone()) { /* there're other threads to run */ flags |= WNOHANG; } retry: TRAP_BEG; #ifdef HAVE_WAITPID result = waitpid(pid, st, flags); #else /* HAVE_WAIT4 */ result = wait4(pid, st, flags, NULL); #endif TRAP_END; if (result < 0) { if (errno == EINTR) { rb_thread_polling(); goto retry; } return -1; } if (result == 0) { if (oflags & WNOHANG) return 0; rb_thread_polling(); if (rb_thread_alone()) flags = oflags; goto retry; } #else /* NO_WAITPID */ if (pid_tbl && st_lookup(pid_tbl, pid, st)) { last_status_set(*st, pid); st_delete(pid_tbl, (st_data_t*)&pid, NULL); return pid; } if (flags) { rb_raise(rb_eArgError, "can't do waitpid with flags"); } for (;;) { TRAP_BEG; result = wait(st); TRAP_END; if (result < 0) { if (errno == EINTR) { rb_thread_schedule(); continue; } return -1; } if (result == pid) { break; } if (!pid_tbl) pid_tbl = st_init_numtable(); st_insert(pid_tbl, pid, st); if (!rb_thread_alone()) rb_thread_schedule(); } #endif if (result > 0) { last_status_set(*st, result); } return result; } #ifdef NO_WAITPID struct wait_data { int pid; int status; }; static int wait_each(pid, status, data) int pid, status; struct wait_data *data; { if (data->status != -1) return ST_STOP; data->pid = pid; data->status = status; return ST_DELETE; } static int waitall_each(pid, status, ary) int pid, status; VALUE ary; { last_status_set(status, pid); rb_ary_push(ary, rb_assoc_new(INT2NUM(pid), rb_last_status)); return ST_DELETE; } #endif /* [MG]:FIXME: I wasn't sure how this should be done, since ::wait() has historically been documented as if it didn't take any arguments despite the fact that it's just an alias for ::waitpid(). The way I have it below is more truthful, but a little confusing. I also took the liberty of putting in the pid values, as they're pretty useful, and it looked as if the original 'ri' output was supposed to contain them after "[...]depending on the value of aPid:". The 'ansi' and 'bs' formats of the ri output don't display the definition list for some reason, but the plain text one does. */ /* * call-seq: * Process.wait() => fixnum * Process.wait(pid=-1, flags=0) => fixnum * Process.waitpid(pid=-1, flags=0) => fixnum * * Waits for a child process to exit, returns its process id, and * sets $? to a Process::Status object * containing information on that process. Which child it waits on * depends on the value of _pid_: * * > 0:: Waits for the child whose process ID equals _pid_. * * 0:: Waits for any child whose process group ID equals that of the * calling process. * * -1:: Waits for any child process (the default if no _pid_ is * given). * * < -1:: Waits for any child whose process group ID equals the absolute * value of _pid_. * * The _flags_ argument may be a logical or of the flag values * Process::WNOHANG (do not block if no child available) * or Process::WUNTRACED (return stopped children that * haven't been reported). Not all flags are available on all * platforms, but a flag value of zero will work on all platforms. * * Calling this method raises a SystemError if there are * no child processes. Not available on all platforms. * * include Process * fork { exit 99 } #=> 27429 * wait #=> 27429 * $?.exitstatus #=> 99 * * pid = fork { sleep 3 } #=> 27440 * Time.now #=> Wed Apr 09 08:57:09 CDT 2003 * waitpid(pid, Process::WNOHANG) #=> nil * Time.now #=> Wed Apr 09 08:57:09 CDT 2003 * waitpid(pid, 0) #=> 27440 * Time.now #=> Wed Apr 09 08:57:12 CDT 2003 */ static VALUE proc_wait(argc, argv) int argc; VALUE *argv; { VALUE vpid, vflags; int pid, flags, status; rb_secure(2); flags = 0; rb_scan_args(argc, argv, "02", &vpid, &vflags); if (argc == 0) { pid = -1; } else { pid = NUM2INT(vpid); if (argc == 2 && !NIL_P(vflags)) { flags = NUM2UINT(vflags); } } if ((pid = rb_waitpid(pid, &status, flags)) < 0) rb_sys_fail(0); if (pid == 0) { return rb_last_status = Qnil; } return INT2FIX(pid); } /* * call-seq: * Process.wait2(pid=-1, flags=0) => [pid, status] * Process.waitpid2(pid=-1, flags=0) => [pid, status] * * Waits for a child process to exit (see Process::waitpid for exact * semantics) and returns an array containing the process id and the * exit status (a Process::Status object) of that * child. Raises a SystemError if there are no child * processes. * * Process.fork { exit 99 } #=> 27437 * pid, status = Process.wait2 * pid #=> 27437 * status.exitstatus #=> 99 */ static VALUE proc_wait2(argc, argv) int argc; VALUE *argv; { VALUE pid = proc_wait(argc, argv); if (NIL_P(pid)) return Qnil; return rb_assoc_new(pid, rb_last_status); } /* * call-seq: * Process.waitall => [ [pid1,status1], ...] * * Waits for all children, returning an array of * _pid_/_status_ pairs (where _status_ is a * Process::Status object). * * fork { sleep 0.2; exit 2 } #=> 27432 * fork { sleep 0.1; exit 1 } #=> 27433 * fork { exit 0 } #=> 27434 * p Process.waitall * * produces: * * [[27434, #], * [27433, #], * [27432, #]] */ static VALUE proc_waitall() { VALUE result; int pid, status; rb_secure(2); result = rb_ary_new(); #ifdef NO_WAITPID if (pid_tbl) { st_foreach(pid_tbl, waitall_each, result); } for (pid = -1;;) { pid = wait(&status); if (pid == -1) { if (errno == ECHILD) break; if (errno == EINTR) { rb_thread_schedule(); continue; } rb_sys_fail(0); } last_status_set(status, pid); rb_ary_push(result, rb_assoc_new(INT2NUM(pid), rb_last_status)); } #else rb_last_status = Qnil; for (pid = -1;;) { pid = rb_waitpid(-1, &status, 0); if (pid == -1) { if (errno == ECHILD) break; rb_sys_fail(0); } rb_ary_push(result, rb_assoc_new(INT2NUM(pid), rb_last_status)); } #endif return result; } static VALUE detach_process_watcher(arg) void *arg; { int pid = (int)(VALUE)arg, status; while (rb_waitpid(pid, &status, WNOHANG) == 0) { rb_thread_sleep(1); } return rb_last_status; } VALUE rb_detach_process(pid) int pid; { return rb_thread_create(detach_process_watcher, (void*)(VALUE)pid); } /* * call-seq: * Process.detach(pid) => thread * * Some operating systems retain the status of terminated child * processes until the parent collects that status (normally using * some variant of wait(). If the parent never collects * this status, the child stays around as a zombie process. * Process::detach prevents this by setting up a * separate Ruby thread whose sole job is to reap the status of the * process _pid_ when it terminates. Use detach * only when you do not intent to explicitly wait for the child to * terminate. detach only checks the status * periodically (currently once each second). * * The waiting thread returns the exit status of the detached process * when it terminates, so you can use Thread#join to * know the result. If specified _pid_ is not a valid child process * ID, the thread returns +nil+ immediately. * * In this first example, we don't reap the first child process, so * it appears as a zombie in the process status display. * * p1 = fork { sleep 0.1 } * p2 = fork { sleep 0.2 } * Process.waitpid(p2) * sleep 2 * system("ps -ho pid,state -p #{p1}") * * produces: * * 27389 Z * * In the next example, Process::detach is used to reap * the child automatically. * * p1 = fork { sleep 0.1 } * p2 = fork { sleep 0.2 } * Process.detach(p1) * Process.waitpid(p2) * sleep 2 * system("ps -ho pid,state -p #{p1}") * * (produces no output) */ static VALUE proc_detach(VALUE obj, VALUE pid) { rb_secure(2); return rb_detach_process(NUM2INT(pid)); } #ifndef HAVE_STRING_H char *strtok(); #endif #ifdef HAVE_SETITIMER #define before_exec() rb_thread_stop_timer() #define after_exec() rb_thread_start_timer() #else #define before_exec() #define after_exec() #endif extern char *dln_find_exe(); static void security(str) char *str; { if (rb_env_path_tainted()) { if (rb_safe_level() > 0) { rb_raise(rb_eSecurityError, "Insecure PATH - %s", str); } } } static int proc_exec_v(argv, prog) char **argv; char *prog; { if (!prog) prog = argv[0]; security(prog); prog = dln_find_exe(prog, 0); if (!prog) return -1; #if (defined(MSDOS) && !defined(DJGPP)) || defined(__human68k__) || defined(__EMX__) || defined(OS2) { #if defined(__human68k__) #define COMMAND "command.x" #endif #if defined(__EMX__) || defined(OS2) /* OS/2 emx */ #define COMMAND "cmd.exe" #endif #if (defined(MSDOS) && !defined(DJGPP)) #define COMMAND "command.com" #endif char *extension; if ((extension = strrchr(prog, '.')) != NULL && strcasecmp(extension, ".bat") == 0) { char **new_argv; char *p; int n; for (n = 0; argv[n]; n++) /* no-op */; new_argv = ALLOCA_N(char*, n + 2); for (; n > 0; n--) new_argv[n + 1] = argv[n]; new_argv[1] = strcpy(ALLOCA_N(char, strlen(argv[0]) + 1), argv[0]); for (p = new_argv[1]; *p != '\0'; p++) if (*p == '/') *p = '\\'; new_argv[0] = COMMAND; argv = new_argv; prog = dln_find_exe(argv[0], 0); if (!prog) { errno = ENOENT; return -1; } } } #endif /* MSDOS or __human68k__ or __EMX__ */ before_exec(); rb_thread_cancel_timer(); execv(prog, argv); preserving_errno(after_exec()); return -1; } static int proc_exec_n(argc, argv, progv) int argc; VALUE *argv; VALUE progv; { char *prog = 0; char **args; int i; if (progv) { prog = RSTRING(progv)->ptr; } args = ALLOCA_N(char*, argc+1); for (i=0; iptr; } args[i] = 0; if (args[0]) { return proc_exec_v(args, prog); } return -1; } int rb_proc_exec(str) const char *str; { const char *s = str; char *ss, *t; char **argv, **a; while (*str && ISSPACE(*str)) str++; #ifdef _WIN32 before_exec(); do_spawn(P_OVERLAY, (char *)str); after_exec(); #else for (s=str; *s; s++) { if (*s != ' ' && !ISALPHA(*s) && strchr("*?{}[]<>()~&|\\$;'`\"\n",*s)) { #if defined(MSDOS) int status; before_exec(); status = system(str); after_exec(); if (status != -1) exit(status); #else #if defined(__human68k__) || defined(__CYGWIN32__) || defined(__EMX__) char *shell = dln_find_exe("sh", 0); int status = -1; before_exec(); if (shell) execl(shell, "sh", "-c", str, (char *) NULL); else status = system(str); after_exec(); if (status != -1) exit(status); #else before_exec(); execl("/bin/sh", "sh", "-c", str, (char *)NULL); preserving_errno(after_exec()); #endif #endif return -1; } } a = argv = ALLOCA_N(char*, (s-str)/2+2); ss = ALLOCA_N(char, s-str+1); strcpy(ss, str); if ((*a++ = strtok(ss, " \t")) != 0) { while ((t = strtok(NULL, " \t")) != 0) { *a++ = t; } *a = NULL; } if (argv[0]) { return proc_exec_v(argv, 0); } errno = ENOENT; #endif /* _WIN32 */ return -1; } #if defined(__human68k__) || defined(__DJGPP__) || defined(_WIN32) static int proc_spawn_v(argv, prog) char **argv; char *prog; { char *extension; int status; if (!prog) prog = argv[0]; security(prog); prog = dln_find_exe(prog, 0); if (!prog) return -1; #if defined(__human68k__) if ((extension = strrchr(prog, '.')) != NULL && strcasecmp(extension, ".bat") == 0) { char **new_argv; char *p; int n; for (n = 0; argv[n]; n++) /* no-op */; new_argv = ALLOCA_N(char*, n + 2); for (; n > 0; n--) new_argv[n + 1] = argv[n]; new_argv[1] = strcpy(ALLOCA_N(char, strlen(argv[0]) + 1), argv[0]); for (p = new_argv[1]; *p != '\0'; p++) if (*p == '/') *p = '\\'; new_argv[0] = COMMAND; argv = new_argv; prog = dln_find_exe(argv[0], 0); if (!prog) { errno = ENOENT; return -1; } } #endif before_exec(); #if defined(_WIN32) status = do_aspawn(P_WAIT, prog, argv); #else status = spawnv(P_WAIT, prog, argv); #endif after_exec(); return status; } static int proc_spawn_n(argc, argv, prog) int argc; VALUE *argv; VALUE prog; { char **args; int i; args = ALLOCA_N(char*, argc + 1); for (i = 0; i < argc; i++) { SafeStringValue(argv[i]); args[i] = StringValueCStr(argv[i]); } if (prog) SafeStringValue(prog); args[i] = (char*) 0; if (args[0]) return proc_spawn_v(args, prog ? StringValueCStr(prog) : 0); return -1; } #if !defined(_WIN32) static int proc_spawn(sv) VALUE sv; { char *str; char *s, *t; char **argv, **a; int status; SafeStringValue(sv); str = s = StringValueCStr(sv); for (s = str; *s; s++) { if (*s != ' ' && !ISALPHA(*s) && strchr("*?{}[]<>()~&|\\$;'`\"\n",*s)) { char *shell = dln_find_exe("sh", 0); before_exec(); status = shell?spawnl(P_WAIT,shell,"sh","-c",str,(char*)NULL):system(str); after_exec(); return status; } } a = argv = ALLOCA_N(char*, (s - str) / 2 + 2); s = ALLOCA_N(char, s - str + 1); strcpy(s, str); if (*a++ = strtok(s, " \t")) { while (t = strtok(NULL, " \t")) *a++ = t; *a = NULL; } return argv[0] ? proc_spawn_v(argv, 0) : -1; } #endif #endif struct rb_exec_arg { int argc; VALUE *argv; VALUE prog; }; static void proc_prepare_args(e, argc, argv, prog) struct rb_exec_arg *e; int argc; VALUE *argv; VALUE prog; { int i; MEMZERO(e, struct rb_exec_arg, 1); if (prog) { SafeStringValue(prog); StringValueCStr(prog); } for (i = 0; i < argc; i++) { SafeStringValue(argv[i]); StringValueCStr(argv[i]); } security(RSTRING(prog ? prog : argv[0])->ptr); e->prog = prog; e->argc = argc; e->argv = argv; } static VALUE proc_exec_args(earg) VALUE earg; { struct rb_exec_arg *e = (struct rb_exec_arg *)earg; int argc = e->argc; VALUE *argv = e->argv; VALUE prog = e->prog; if (argc == 1 && prog == 0) { return (VALUE)rb_proc_exec(RSTRING(argv[0])->ptr); } else { return (VALUE)proc_exec_n(argc, argv, prog); } } /* * call-seq: * exec(command [, arg, ...]) * * Replaces the current process by running the given external _command_. * If +exec+ is given a single argument, that argument is * taken as a line that is subject to shell expansion before being * executed. If multiple arguments are given, the second and subsequent * arguments are passed as parameters to _command_ with no shell * expansion. If the first argument is a two-element array, the first * element is the command to be executed, and the second argument is * used as the argv[0] value, which may show up in process * listings. In MSDOS environments, the command is executed in a * subshell; otherwise, one of the exec(2) system calls is * used, so the running command may inherit some of the environment of * the original program (including open file descriptors). * * exec "echo *" # echoes list of files in current directory * # never get here * * * exec "echo", "*" # echoes an asterisk * # never get here */ VALUE rb_f_exec(argc, argv) int argc; VALUE *argv; { VALUE prog = 0; VALUE tmp; struct rb_exec_arg earg; if (argc == 0) { rb_last_status = Qnil; rb_raise(rb_eArgError, "wrong number of arguments"); } tmp = rb_check_array_type(argv[0]); if (!NIL_P(tmp)) { if (RARRAY(tmp)->len != 2) { rb_raise(rb_eArgError, "wrong first argument"); } prog = RARRAY(tmp)->ptr[0]; argv[0] = RARRAY(tmp)->ptr[1]; SafeStringValue(prog); } proc_prepare_args(&earg, argc, argv, prog); proc_exec_args((VALUE)&earg); rb_sys_fail(RSTRING(argv[0])->ptr); return Qnil; /* dummy */ } /* * call-seq: * Kernel.fork [{ block }] => fixnum or nil * Process.fork [{ block }] => fixnum or nil * * Creates a subprocess. If a block is specified, that block is run * in the subprocess, and the subprocess terminates with a status of * zero. Otherwise, the +fork+ call returns twice, once in * the parent, returning the process ID of the child, and once in * the child, returning _nil_. The child process can exit using * Kernel.exit! to avoid running any * at_exit functions. The parent process should * use Process.wait to collect the termination statuses * of its children or use Process.detach to register * disinterest in their status; otherwise, the operating system * may accumulate zombie processes. * * The thread calling fork is the only thread in the created child process. * fork doesn't copy other threads. */ static VALUE rb_f_fork(obj) VALUE obj; { #if !defined(__human68k__) && !defined(_WIN32) && !defined(__MACOS__) && !defined(__EMX__) && !defined(__VMS) int pid; rb_secure(2); #ifndef __VMS fflush(stdout); fflush(stderr); #endif switch (pid = fork()) { case 0: #ifdef linux after_exec(); #endif rb_thread_atfork(); if (rb_block_given_p()) { int status; rb_protect(rb_yield, Qundef, &status); ruby_stop(status); } return Qnil; case -1: rb_sys_fail("fork(2)"); return Qnil; default: return INT2FIX(pid); } #else rb_notimplement(); #endif } /* * call-seq: * Process.exit!(fixnum=-1) * * Exits the process immediately. No exit handlers are * run. fixnum is returned to the underlying system as the * exit status. * * Process.exit!(0) */ static VALUE rb_f_exit_bang(argc, argv, obj) int argc; VALUE *argv; VALUE obj; { VALUE status; int istatus; rb_secure(4); if (rb_scan_args(argc, argv, "01", &status) == 1) { switch (status) { case Qtrue: istatus = EXIT_SUCCESS; break; case Qfalse: istatus = EXIT_FAILURE; break; default: istatus = NUM2INT(status); break; } } else { istatus = EXIT_FAILURE; } _exit(istatus); return Qnil; /* not reached */ } #if defined(sun) #define signal(a,b) sigset(a,b) #endif void rb_syswait(pid) int pid; { static int overriding; #ifdef SIGHUP RETSIGTYPE (*hfunc)_((int)); #endif #ifdef SIGQUIT RETSIGTYPE (*qfunc)_((int)); #endif RETSIGTYPE (*ifunc)_((int)); int status; int i, hooked = Qfalse; if (!overriding) { #ifdef SIGHUP hfunc = signal(SIGHUP, SIG_IGN); #endif #ifdef SIGQUIT qfunc = signal(SIGQUIT, SIG_IGN); #endif ifunc = signal(SIGINT, SIG_IGN); overriding = Qtrue; hooked = Qtrue; } do { i = rb_waitpid(pid, &status, 0); } while (i == -1 && errno == EINTR); if (hooked) { #ifdef SIGHUP signal(SIGHUP, hfunc); #endif #ifdef SIGQUIT signal(SIGQUIT, qfunc); #endif signal(SIGINT, ifunc); overriding = Qfalse; } } /* * call-seq: * system(cmd [, arg, ...]) => true or false * * Executes _cmd_ in a subshell, returning +true+ if * the command was found and ran successfully, +false+ * otherwise. An error status is available in $?. The * arguments are processed in the same way as for * Kernel::exec. * * system("echo *") * system("echo", "*") * * produces: * * config.h main.rb * * */ static VALUE rb_f_system(argc, argv) int argc; VALUE *argv; { int status; #if defined(__EMX__) VALUE cmd; fflush(stdout); fflush(stderr); if (argc == 0) { rb_last_status = Qnil; rb_raise(rb_eArgError, "wrong number of arguments"); } if (TYPE(argv[0]) == T_ARRAY) { if (RARRAY(argv[0])->len != 2) { rb_raise(rb_eArgError, "wrong first argument"); } argv[0] = RARRAY(argv[0])->ptr[0]; } cmd = rb_ary_join(rb_ary_new4(argc, argv), rb_str_new2(" ")); SafeStringValue(cmd); status = do_spawn(RSTRING(cmd)->ptr); last_status_set(status, 0); #elif defined(__human68k__) || defined(__DJGPP__) || defined(_WIN32) volatile VALUE prog = 0; fflush(stdout); fflush(stderr); if (argc == 0) { rb_last_status = Qnil; rb_raise(rb_eArgError, "wrong number of arguments"); } if (TYPE(argv[0]) == T_ARRAY) { if (RARRAY(argv[0])->len != 2) { rb_raise(rb_eArgError, "wrong first argument"); } prog = RARRAY(argv[0])->ptr[0]; argv[0] = RARRAY(argv[0])->ptr[1]; } if (argc == 1 && prog == 0) { #if defined(_WIN32) SafeStringValue(argv[0]); status = do_spawn(P_WAIT, StringValueCStr(argv[0])); #else status = proc_spawn(argv[0]); #endif } else { status = proc_spawn_n(argc, argv, prog); } #if !defined(_WIN32) last_status_set(status == -1 ? 127 : status, 0); #else if (status == -1) last_status_set(0x7f << 8, 0); #endif #elif defined(__VMS) VALUE cmd; if (argc == 0) { rb_last_status = Qnil; rb_raise(rb_eArgError, "wrong number of arguments"); } if (TYPE(argv[0]) == T_ARRAY) { if (RARRAY(argv[0])->len != 2) { rb_raise(rb_eArgError, "wrong first argument"); } argv[0] = RARRAY(argv[0])->ptr[0]; } cmd = rb_ary_join(rb_ary_new4(argc, argv), rb_str_new2(" ")); SafeStringValue(cmd); status = system(StringValueCStr(cmd)); last_status_set((status & 0xff) << 8, 0); #else volatile VALUE prog = 0; int pid; struct rb_exec_arg earg; RETSIGTYPE (*chfunc)(int); fflush(stdout); fflush(stderr); if (argc == 0) { rb_last_status = Qnil; rb_raise(rb_eArgError, "wrong number of arguments"); } if (TYPE(argv[0]) == T_ARRAY) { if (RARRAY(argv[0])->len != 2) { rb_raise(rb_eArgError, "wrong first argument"); } prog = RARRAY(argv[0])->ptr[0]; argv[0] = RARRAY(argv[0])->ptr[1]; } proc_prepare_args(&earg, argc, argv, prog); chfunc = signal(SIGCHLD, SIG_DFL); retry: pid = fork(); if (pid == 0) { /* child process */ rb_thread_atfork(); rb_protect(proc_exec_args, (VALUE)&earg, NULL); _exit(127); } if (pid < 0) { if (errno == EAGAIN) { rb_thread_sleep(1); goto retry; } } else { rb_syswait(pid); } signal(SIGCHLD, chfunc); if (pid < 0) rb_sys_fail(0); status = NUM2INT(rb_last_status); #endif if (status == EXIT_SUCCESS) return Qtrue; return Qfalse; } /* * call-seq: * sleep([duration]) => fixnum * * Suspends the current thread for _duration_ seconds (which may be any number, * including a +Float+ with fractional seconds). Returns the actual number of * seconds slept (rounded), which may be less than that asked for if another * thread calls Thread#run. Zero arguments causes +sleep+ to sleep * forever. * * Time.new #=> Wed Apr 09 08:56:32 CDT 2003 * sleep 1.2 #=> 1 * Time.new #=> Wed Apr 09 08:56:33 CDT 2003 * sleep 1.9 #=> 2 * Time.new #=> Wed Apr 09 08:56:35 CDT 2003 */ static VALUE rb_f_sleep(argc, argv) int argc; VALUE *argv; { int beg, end; beg = time(0); if (argc == 0) { rb_thread_sleep_forever(); } else if (argc == 1) { rb_thread_wait_for(rb_time_interval(argv[0])); } else { rb_raise(rb_eArgError, "wrong number of arguments"); } end = time(0) - beg; return INT2FIX(end); } /* * call-seq: * Process.getpgrp => integer * * Returns the process group ID for this process. Not available on * all platforms. * * Process.getpgid(0) #=> 25527 * Process.getpgrp #=> 25527 */ #if defined(SIGCLD) && !defined(SIGCHLD) # define SIGCHLD SIGCLD #endif static VALUE proc_getpgrp() { int pgrp; rb_secure(2); #if defined(HAVE_GETPGRP) && defined(GETPGRP_VOID) pgrp = getpgrp(); if (pgrp < 0) rb_sys_fail(0); return INT2FIX(pgrp); #else # ifdef HAVE_GETPGID pgrp = getpgid(0); if (pgrp < 0) rb_sys_fail(0); return INT2FIX(pgrp); # else rb_notimplement(); # endif #endif } /* * call-seq: * Process.setpgrp => 0 * * Equivalent to setpgid(0,0). Not available on all * platforms. */ static VALUE proc_setpgrp() { rb_secure(2); /* check for posix setpgid() first; this matches the posix */ /* getpgrp() above. It appears that configure will set SETPGRP_VOID */ /* even though setpgrp(0,0) would be prefered. The posix call avoids */ /* this confusion. */ #ifdef HAVE_SETPGID if (setpgid(0,0) < 0) rb_sys_fail(0); #elif defined(HAVE_SETPGRP) && defined(SETPGRP_VOID) if (setpgrp() < 0) rb_sys_fail(0); #else rb_notimplement(); #endif return INT2FIX(0); } /* * call-seq: * Process.getpgid(pid) => integer * * Returns the process group ID for the given process id. Not * available on all platforms. * * Process.getpgid(Process.ppid()) #=> 25527 */ static VALUE proc_getpgid(obj, pid) VALUE obj, pid; { #if defined(HAVE_GETPGID) && !defined(__CHECKER__) int i; rb_secure(2); i = getpgid(NUM2INT(pid)); if (i < 0) rb_sys_fail(0); return INT2NUM(i); #else rb_notimplement(); #endif } /* * call-seq: * Process.setpgid(pid, integer) => 0 * * Sets the process group ID of _pid_ (0 indicates this * process) to integer. Not available on all platforms. */ static VALUE proc_setpgid(obj, pid, pgrp) VALUE obj, pid, pgrp; { #ifdef HAVE_SETPGID int ipid, ipgrp; rb_secure(2); ipid = NUM2INT(pid); ipgrp = NUM2INT(pgrp); if (setpgid(ipid, ipgrp) < 0) rb_sys_fail(0); return INT2FIX(0); #else rb_notimplement(); #endif } /* * call-seq: * Process.setsid => fixnum * * Establishes this process as a new session and process group * leader, with no controlling tty. Returns the session id. Not * available on all platforms. * * Process.setsid #=> 27422 */ static VALUE proc_setsid() { #if defined(HAVE_SETSID) int pid; rb_secure(2); pid = setsid(); if (pid < 0) rb_sys_fail(0); return INT2FIX(pid); #elif defined(HAVE_SETPGRP) && defined(TIOCNOTTY) rb_pid_t pid; int ret; rb_secure(2); pid = getpid(); #if defined(SETPGRP_VOID) ret = setpgrp(); /* If `pid_t setpgrp(void)' is equivalent to setsid(), `ret' will be the same value as `pid', and following open() will fail. In Linux, `int setpgrp(void)' is equivalent to setpgid(0, 0). */ #else ret = setpgrp(0, pid); #endif if (ret == -1) rb_sys_fail(0); if ((fd = open("/dev/tty", O_RDWR)) >= 0) { ioctl(fd, TIOCNOTTY, NULL); close(fd); } return INT2FIX(pid); #else rb_notimplement(); #endif } /* * call-seq: * Process.getpriority(kind, integer) => fixnum * * Gets the scheduling priority for specified process, process group, * or user. kind indicates the kind of entity to find: one * of Process::PRIO_PGRP, * Process::PRIO_USER, or * Process::PRIO_PROCESS. _integer_ is an id * indicating the particular process, process group, or user (an id * of 0 means _current_). Lower priorities are more favorable * for scheduling. Not available on all platforms. * * Process.getpriority(Process::PRIO_USER, 0) #=> 19 * Process.getpriority(Process::PRIO_PROCESS, 0) #=> 19 */ static VALUE proc_getpriority(obj, which, who) VALUE obj, which, who; { #ifdef HAVE_GETPRIORITY int prio, iwhich, iwho; rb_secure(2); iwhich = NUM2INT(which); iwho = NUM2INT(who); errno = 0; prio = getpriority(iwhich, iwho); if (errno) rb_sys_fail(0); return INT2FIX(prio); #else rb_notimplement(); #endif } /* * call-seq: * Process.setpriority(kind, integer, priority) => 0 * * See Process#getpriority. * * Process.setpriority(Process::PRIO_USER, 0, 19) #=> 0 * Process.setpriority(Process::PRIO_PROCESS, 0, 19) #=> 0 * Process.getpriority(Process::PRIO_USER, 0) #=> 19 * Process.getpriority(Process::PRIO_PROCESS, 0) #=> 19 */ static VALUE proc_setpriority(obj, which, who, prio) VALUE obj, which, who, prio; { #ifdef HAVE_GETPRIORITY int iwhich, iwho, iprio; rb_secure(2); iwhich = NUM2INT(which); iwho = NUM2INT(who); iprio = NUM2INT(prio); if (setpriority(iwhich, iwho, iprio) < 0) rb_sys_fail(0); return INT2FIX(0); #else rb_notimplement(); #endif } #if SIZEOF_RLIM_T == SIZEOF_INT # define RLIM2NUM(v) UINT2NUM(v) # define NUM2RLIM(v) NUM2UINT(v) #elif SIZEOF_RLIM_T == SIZEOF_LONG # define RLIM2NUM(v) ULONG2NUM(v) # define NUM2RLIM(v) NUM2ULONG(v) #elif SIZEOF_RLIM_T == SIZEOF_LONG_LONG # define RLIM2NUM(v) ULL2NUM(v) # define NUM2RLIM(v) NUM2ULL(v) #endif /* * call-seq: * Process.getrlimit(resource) => [cur_limit, max_limit] * * Gets the resource limit of the process. * _cur_limit_ means current (soft) limit and * _max_limit_ means maximum (hard) limit. * * _resource_ indicates the kind of resource to limit: * such as Process::RLIMIT_CORE, * Process::RLIMIT_CPU, etc. * See Process.setrlimit for details. * * _cur_limit_ and _max_limit_ may be Process::RLIM_INFINITY, * Process::RLIM_SAVED_MAX or * Process::RLIM_SAVED_CUR. * See Process.setrlimit and the system getrlimit(2) manual for details. */ static VALUE proc_getrlimit(VALUE obj, VALUE resource) { #if defined(HAVE_GETRLIMIT) && defined(RLIM2NUM) struct rlimit rlim; rb_secure(2); if (getrlimit(NUM2INT(resource), &rlim) < 0) { rb_sys_fail("getrlimit"); } return rb_assoc_new(RLIM2NUM(rlim.rlim_cur), RLIM2NUM(rlim.rlim_max)); #else rb_notimplement(); #endif } /* * call-seq: * Process.setrlimit(resource, cur_limit, max_limit) => nil * Process.setrlimit(resource, cur_limit) => nil * * Sets the resource limit of the process. * _cur_limit_ means current (soft) limit and * _max_limit_ means maximum (hard) limit. * * If _max_limit_ is not given, _cur_limit_ is used. * * _resource_ indicates the kind of resource to limit. * The list of resources are OS dependent. * Ruby may support following resources. * * [Process::RLIMIT_CORE] core size (bytes) (SUSv3) * [Process::RLIMIT_CPU] CPU time (seconds) (SUSv3) * [Process::RLIMIT_DATA] data segment (bytes) (SUSv3) * [Process::RLIMIT_FSIZE] file size (bytes) (SUSv3) * [Process::RLIMIT_NOFILE] file descriptors (number) (SUSv3) * [Process::RLIMIT_STACK] stack size (bytes) (SUSv3) * [Process::RLIMIT_AS] total available memory (bytes) (SUSv3, NetBSD, FreeBSD, OpenBSD but 4.4BSD-Lite) * [Process::RLIMIT_MEMLOCK] total size for mlock(2) (bytes) (4.4BSD, GNU/Linux) * [Process::RLIMIT_NPROC] number of processes for the user (number) (4.4BSD, GNU/Linux) * [Process::RLIMIT_RSS] resident memory size (bytes) (4.2BSD, GNU/Linux) * [Process::RLIMIT_SBSIZE] all socket buffers (bytes) (NetBSD, FreeBSD) * * Other Process::RLIMIT_??? constants may be defined. * * _cur_limit_ and _max_limit_ may be Process::RLIM_INFINITY, * which means that the resource is not limited. * They may be Process::RLIM_SAVED_MAX or * Process::RLIM_SAVED_CUR too. * See system setrlimit(2) manual for details. * */ static VALUE proc_setrlimit(int argc, VALUE *argv, VALUE obj) { #if defined(HAVE_SETRLIMIT) && defined(NUM2RLIM) VALUE resource, rlim_cur, rlim_max; struct rlimit rlim; rb_secure(2); rb_scan_args(argc, argv, "21", &resource, &rlim_cur, &rlim_max); if (rlim_max == Qnil) rlim_max = rlim_cur; rlim.rlim_cur = NUM2RLIM(rlim_cur); rlim.rlim_max = NUM2RLIM(rlim_max); if (setrlimit(NUM2INT(resource), &rlim) < 0) { rb_sys_fail("setrlimit"); } return Qnil; #else rb_notimplement(); #endif } static int under_uid_switch = 0; static void check_uid_switch() { rb_secure(2); if (under_uid_switch) { rb_raise(rb_eRuntimeError, "can't handle UID while evaluating block given to Process::UID.switch method"); } } static int under_gid_switch = 0; static void check_gid_switch() { rb_secure(2); if (under_gid_switch) { rb_raise(rb_eRuntimeError, "can't handle GID while evaluating block given to Process::UID.switch method"); } } /********************************************************************* * Document-class: Process::Sys * * The Process::Sys module contains UID and GID * functions which provide direct bindings to the system calls of the * same names instead of the more-portable versions of the same * functionality found in the Process, * Process::UID, and Process::GID modules. */ /* * call-seq: * Process::Sys.setuid(integer) => nil * * Set the user ID of the current process to _integer_. Not * available on all platforms. * */ static VALUE p_sys_setuid(obj, id) VALUE obj, id; { #if defined HAVE_SETUID check_uid_switch(); if (setuid(NUM2INT(id)) != 0) rb_sys_fail(0); #else rb_notimplement(); #endif return Qnil; } /* * call-seq: * Process::Sys.setruid(integer) => nil * * Set the real user ID of the calling process to _integer_. * Not available on all platforms. * */ static VALUE p_sys_setruid(obj, id) VALUE obj, id; { #if defined HAVE_SETRUID check_uid_switch(); if (setruid(NUM2INT(id)) != 0) rb_sys_fail(0); #else rb_notimplement(); #endif return Qnil; } /* * call-seq: * Process::Sys.seteuid(integer) => nil * * Set the effective user ID of the calling process to * _integer_. Not available on all platforms. * */ static VALUE p_sys_seteuid(obj, id) VALUE obj, id; { #if defined HAVE_SETEUID check_uid_switch(); if (seteuid(NUM2INT(id)) != 0) rb_sys_fail(0); #else rb_notimplement(); #endif return Qnil; } /* * call-seq: * Process::Sys.setreuid(rid, eid) => nil * * Sets the (integer) real and/or effective user IDs of the current * process to _rid_ and _eid_, respectively. A value of * -1 for either means to leave that ID unchanged. Not * available on all platforms. * */ static VALUE p_sys_setreuid(obj, rid, eid) VALUE obj, rid, eid; { #if defined HAVE_SETREUID check_uid_switch(); if (setreuid(NUM2INT(rid),NUM2INT(eid)) != 0) rb_sys_fail(0); #else rb_notimplement(); #endif return Qnil; } /* * call-seq: * Process::Sys.setresuid(rid, eid, sid) => nil * * Sets the (integer) real, effective, and saved user IDs of the * current process to _rid_, _eid_, and _sid_ respectively. A * value of -1 for any value means to * leave that ID unchanged. Not available on all platforms. * */ static VALUE p_sys_setresuid(obj, rid, eid, sid) VALUE obj, rid, eid, sid; { #if defined HAVE_SETRESUID check_uid_switch(); if (setresuid(NUM2INT(rid),NUM2INT(eid),NUM2INT(sid)) != 0) rb_sys_fail(0); #else rb_notimplement(); #endif return Qnil; } /* * call-seq: * Process.uid => fixnum * Process::UID.rid => fixnum * Process::Sys.getuid => fixnum * * Returns the (real) user ID of this process. * * Process.uid #=> 501 */ static VALUE proc_getuid(obj) VALUE obj; { int uid = getuid(); return INT2FIX(uid); } /* * call-seq: * Process.uid= integer => numeric * * Sets the (integer) user ID for this process. Not available on all * platforms. */ static VALUE proc_setuid(obj, id) VALUE obj, id; { int uid = NUM2INT(id); check_uid_switch(); #if defined(HAVE_SETRESUID) && !defined(__CHECKER__) if (setresuid(uid, -1, -1) < 0) rb_sys_fail(0); #elif defined HAVE_SETREUID if (setreuid(uid, -1) < 0) rb_sys_fail(0); #elif defined HAVE_SETRUID if (setruid(uid) < 0) rb_sys_fail(0); #elif defined HAVE_SETUID { if (geteuid() == uid) { if (setuid(uid) < 0) rb_sys_fail(0); } else { rb_notimplement(); } } #else rb_notimplement(); #endif return INT2FIX(uid); } /******************************************************************** * * Document-class: Process::UID * * The Process::UID module contains a collection of * module functions which can be used to portably get, set, and * switch the current process's real, effective, and saved user IDs. * */ static int SAVED_USER_ID = -1; #ifdef BROKEN_SETREUID int setreuid(ruid, euid) rb_uid_t ruid, euid; { if (ruid != -1 && ruid != getuid()) { if (euid == -1) euid = geteuid(); if (setuid(ruid) < 0) return -1; } if (euid != -1 && euid != geteuid()) { if (seteuid(euid) < 0) return -1; } return 0; } #endif /* * call-seq: * Process::UID.change_privilege(integer) => fixnum * * Change the current process's real and effective user ID to that * specified by _integer_. Returns the new user ID. Not * available on all platforms. * * [Process.uid, Process.euid] #=> [0, 0] * Process::UID.change_privilege(31) #=> 31 * [Process.uid, Process.euid] #=> [31, 31] */ static VALUE p_uid_change_privilege(obj, id) VALUE obj, id; { int uid; check_uid_switch(); uid = NUM2INT(id); if (geteuid() == 0) { /* root-user */ #if defined(HAVE_SETRESUID) if (setresuid(uid, uid, uid) < 0) rb_sys_fail(0); SAVED_USER_ID = uid; #elif defined(HAVE_SETUID) if (setuid(uid) < 0) rb_sys_fail(0); SAVED_USER_ID = uid; #elif defined(HAVE_SETREUID) && !defined(OBSOLETE_SETREUID) if (getuid() == uid) { if (SAVED_USER_ID == uid) { if (setreuid(-1, uid) < 0) rb_sys_fail(0); } else { if (uid == 0) { /* (r,e,s) == (root, root, x) */ if (setreuid(-1, SAVED_USER_ID) < 0) rb_sys_fail(0); if (setreuid(SAVED_USER_ID, 0) < 0) rb_sys_fail(0); SAVED_USER_ID = 0; /* (r,e,s) == (x, root, root) */ if (setreuid(uid, uid) < 0) rb_sys_fail(0); SAVED_USER_ID = uid; } else { if (setreuid(0, -1) < 0) rb_sys_fail(0); SAVED_USER_ID = 0; if (setreuid(uid, uid) < 0) rb_sys_fail(0); SAVED_USER_ID = uid; } } } else { if (setreuid(uid, uid) < 0) rb_sys_fail(0); SAVED_USER_ID = uid; } #elif defined(HAVE_SETRUID) && defined(HAVE_SETEUID) if (getuid() == uid) { if (SAVED_USER_ID == uid) { if (seteuid(uid) < 0) rb_sys_fail(0); } else { if (uid == 0) { if (setruid(SAVED_USER_ID) < 0) rb_sys_fail(0); SAVED_USER_ID = 0; if (setruid(0) < 0) rb_sys_fail(0); } else { if (setruid(0) < 0) rb_sys_fail(0); SAVED_USER_ID = 0; if (seteuid(uid) < 0) rb_sys_fail(0); if (setruid(uid) < 0) rb_sys_fail(0); SAVED_USER_ID = uid; } } } else { if (seteuid(uid) < 0) rb_sys_fail(0); if (setruid(uid) < 0) rb_sys_fail(0); SAVED_USER_ID = uid; } #else rb_notimplement(); #endif } else { /* unprivileged user */ #if defined(HAVE_SETRESUID) if (setresuid((getuid() == uid)? -1: uid, (geteuid() == uid)? -1: uid, (SAVED_USER_ID == uid)? -1: uid) < 0) rb_sys_fail(0); SAVED_USER_ID = uid; #elif defined(HAVE_SETREUID) && !defined(OBSOLETE_SETREUID) if (SAVED_USER_ID == uid) { if (setreuid((getuid() == uid)? -1: uid, (geteuid() == uid)? -1: uid) < 0) rb_sys_fail(0); } else if (getuid() != uid) { if (setreuid(uid, (geteuid() == uid)? -1: uid) < 0) rb_sys_fail(0); SAVED_USER_ID = uid; } else if (/* getuid() == uid && */ geteuid() != uid) { if (setreuid(geteuid(), uid) < 0) rb_sys_fail(0); SAVED_USER_ID = uid; if (setreuid(uid, -1) < 0) rb_sys_fail(0); } else { /* getuid() == uid && geteuid() == uid */ if (setreuid(-1, SAVED_USER_ID) < 0) rb_sys_fail(0); if (setreuid(SAVED_USER_ID, uid) < 0) rb_sys_fail(0); SAVED_USER_ID = uid; if (setreuid(uid, -1) < 0) rb_sys_fail(0); } #elif defined(HAVE_SETRUID) && defined(HAVE_SETEUID) if (SAVED_USER_ID == uid) { if (geteuid() != uid && seteuid(uid) < 0) rb_sys_fail(0); if (getuid() != uid && setruid(uid) < 0) rb_sys_fail(0); } else if (/* SAVED_USER_ID != uid && */ geteuid() == uid) { if (getuid() != uid) { if (setruid(uid) < 0) rb_sys_fail(0); SAVED_USER_ID = uid; } else { if (setruid(SAVED_USER_ID) < 0) rb_sys_fail(0); SAVED_USER_ID = uid; if (setruid(uid) < 0) rb_sys_fail(0); } } else if (/* geteuid() != uid && */ getuid() == uid) { if (seteuid(uid) < 0) rb_sys_fail(0); if (setruid(SAVED_USER_ID) < 0) rb_sys_fail(0); SAVED_USER_ID = uid; if (setruid(uid) < 0) rb_sys_fail(0); } else { errno = EPERM; rb_sys_fail(0); } #elif defined HAVE_44BSD_SETUID if (getuid() == uid) { /* (r,e,s)==(uid,?,?) ==> (uid,uid,uid) */ if (setuid(uid) < 0) rb_sys_fail(0); SAVED_USER_ID = uid; } else { errno = EPERM; rb_sys_fail(0); } #elif defined HAVE_SETEUID if (getuid() == uid && SAVED_USER_ID == uid) { if (seteuid(uid) < 0) rb_sys_fail(0); } else { errno = EPERM; rb_sys_fail(0); } #elif defined HAVE_SETUID if (getuid() == uid && SAVED_USER_ID == uid) { if (setuid(uid) < 0) rb_sys_fail(0); } else { errno = EPERM; rb_sys_fail(0); } #else rb_notimplement(); #endif } return INT2FIX(uid); } /* * call-seq: * Process::Sys.setgid(integer) => nil * * Set the group ID of the current process to _integer_. Not * available on all platforms. * */ static VALUE p_sys_setgid(obj, id) VALUE obj, id; { #if defined HAVE_SETGID check_gid_switch(); if (setgid(NUM2INT(id)) != 0) rb_sys_fail(0); #else rb_notimplement(); #endif return Qnil; } /* * call-seq: * Process::Sys.setrgid(integer) => nil * * Set the real group ID of the calling process to _integer_. * Not available on all platforms. * */ static VALUE p_sys_setrgid(obj, id) VALUE obj, id; { #if defined HAVE_SETRGID check_gid_switch(); if (setrgid(NUM2INT(id)) != 0) rb_sys_fail(0); #else rb_notimplement(); #endif return Qnil; } /* * call-seq: * Process::Sys.setegid(integer) => nil * * Set the effective group ID of the calling process to * _integer_. Not available on all platforms. * */ static VALUE p_sys_setegid(obj, id) VALUE obj, id; { #if defined HAVE_SETEGID check_gid_switch(); if (setegid(NUM2INT(id)) != 0) rb_sys_fail(0); #else rb_notimplement(); #endif return Qnil; } /* * call-seq: * Process::Sys.setregid(rid, eid) => nil * * Sets the (integer) real and/or effective group IDs of the current * process to rid and eid, respectively. A value of * -1 for either means to leave that ID unchanged. Not * available on all platforms. * */ static VALUE p_sys_setregid(obj, rid, eid) VALUE obj, rid, eid; { #if defined HAVE_SETREGID check_gid_switch(); if (setregid(NUM2INT(rid),NUM2INT(eid)) != 0) rb_sys_fail(0); #else rb_notimplement(); #endif return Qnil; } /* * call-seq: * Process::Sys.setresgid(rid, eid, sid) => nil * * Sets the (integer) real, effective, and saved user IDs of the * current process to rid, eid, and sid * respectively. A value of -1 for any value means to * leave that ID unchanged. Not available on all platforms. * */ static VALUE p_sys_setresgid(obj, rid, eid, sid) VALUE obj, rid, eid, sid; { #if defined HAVE_SETRESGID check_gid_switch(); if (setresgid(NUM2INT(rid),NUM2INT(eid),NUM2INT(sid)) != 0) rb_sys_fail(0); #else rb_notimplement(); #endif return Qnil; } /* * call-seq: * Process::Sys.issetugid => true or false * * Returns +true+ if the process was created as a result * of an execve(2) system call which had either of the setuid or * setgid bits set (and extra privileges were given as a result) or * if it has changed any of its real, effective or saved user or * group IDs since it began execution. * */ static VALUE p_sys_issetugid(obj) VALUE obj; { #if defined HAVE_ISSETUGID rb_secure(2); if (issetugid()) { return Qtrue; } else { return Qfalse; } #else rb_notimplement(); return Qnil; /* not reached */ #endif } /* * call-seq: * Process.gid => fixnum * Process::GID.rid => fixnum * Process::Sys.getgid => fixnum * * Returns the (real) group ID for this process. * * Process.gid #=> 500 */ static VALUE proc_getgid(obj) VALUE obj; { int gid = getgid(); return INT2FIX(gid); } /* * call-seq: * Process.gid= fixnum => fixnum * * Sets the group ID for this process. */ static VALUE proc_setgid(obj, id) VALUE obj, id; { int gid = NUM2INT(id); check_gid_switch(); #if defined(HAVE_SETRESGID) && !defined(__CHECKER__) if (setresgid(gid, -1, -1) < 0) rb_sys_fail(0); #elif defined HAVE_SETREGID if (setregid(gid, -1) < 0) rb_sys_fail(0); #elif defined HAVE_SETRGID if (setrgid(gid) < 0) rb_sys_fail(0); #elif defined HAVE_SETGID { if (getegid() == gid) { if (setgid(gid) < 0) rb_sys_fail(0); } else { rb_notimplement(); } } #else rb_notimplement(); #endif return INT2FIX(gid); } static size_t maxgroups = 32; /* * call-seq: * Process.groups => array * * Get an Array of the gids of groups in the * supplemental group access list for this process. * * Process.groups #=> [27, 6, 10, 11] * */ static VALUE proc_getgroups(VALUE obj) { #ifdef HAVE_GETGROUPS VALUE ary; size_t ngroups; rb_gid_t *groups; int i; groups = ALLOCA_N(rb_gid_t, maxgroups); ngroups = getgroups(maxgroups, groups); if (ngroups == -1) rb_sys_fail(0); ary = rb_ary_new(); for (i = 0; i < ngroups; i++) rb_ary_push(ary, INT2NUM(groups[i])); return ary; #else rb_notimplement(); return Qnil; #endif } /* * call-seq: * Process.groups= array => array * * Set the supplemental group access list to the given * Array of group IDs. * * Process.groups #=> [0, 1, 2, 3, 4, 6, 10, 11, 20, 26, 27] * Process.groups = [27, 6, 10, 11] #=> [27, 6, 10, 11] * Process.groups #=> [27, 6, 10, 11] * */ static VALUE proc_setgroups(VALUE obj, VALUE ary) { #ifdef HAVE_SETGROUPS size_t ngroups; rb_gid_t *groups; int i; struct group *gr; Check_Type(ary, T_ARRAY); ngroups = RARRAY(ary)->len; if (ngroups > maxgroups) rb_raise(rb_eArgError, "too many groups, %d max", maxgroups); groups = ALLOCA_N(rb_gid_t, ngroups); for (i = 0; i < ngroups && i < RARRAY(ary)->len; i++) { VALUE g = RARRAY(ary)->ptr[i]; if (FIXNUM_P(g)) { groups[i] = FIX2INT(g); } else { VALUE tmp = rb_check_string_type(g); if (NIL_P(tmp)) { groups[i] = NUM2INT(g); } else { gr = getgrnam(RSTRING(tmp)->ptr); if (gr == NULL) rb_raise(rb_eArgError, "can't find group for %s", RSTRING(tmp)->ptr); groups[i] = gr->gr_gid; } } } i = setgroups(ngroups, groups); if (i == -1) rb_sys_fail(0); return proc_getgroups(obj); #else rb_notimplement(); return Qnil; #endif } /* * call-seq: * Process.initgroups(username, gid) => array * * Initializes the supplemental group access list by reading the * system group database and using all groups of which the given user * is a member. The group with the specified gid is also * added to the list. Returns the resulting Array of the * gids of all the groups in the supplementary group access list. Not * available on all platforms. * * Process.groups #=> [0, 1, 2, 3, 4, 6, 10, 11, 20, 26, 27] * Process.initgroups( "mgranger", 30 ) #=> [30, 6, 10, 11] * Process.groups #=> [30, 6, 10, 11] * */ static VALUE proc_initgroups(obj, uname, base_grp) VALUE obj, uname, base_grp; { #ifdef HAVE_INITGROUPS if (initgroups(StringValuePtr(uname), (rb_gid_t)NUM2INT(base_grp)) != 0) { rb_sys_fail(0); } return proc_getgroups(obj); #else rb_notimplement(); return Qnil; #endif } /* * call-seq: * Process.maxgroups => fixnum * * Returns the maximum number of gids allowed in the supplemental * group access list. * * Process.maxgroups #=> 32 */ static VALUE proc_getmaxgroups(obj) VALUE obj; { return INT2FIX(maxgroups); } /* * call-seq: * Process.maxgroups= fixnum => fixnum * * Sets the maximum number of gids allowed in the supplemental group * access list. */ static VALUE proc_setmaxgroups(VALUE obj, VALUE val) { size_t ngroups = FIX2INT(val); if (ngroups > 4096) ngroups = 4096; maxgroups = ngroups; return INT2FIX(maxgroups); } /******************************************************************** * * Document-class: Process::GID * * The Process::GID module contains a collection of * module functions which can be used to portably get, set, and * switch the current process's real, effective, and saved group IDs. * */ static int SAVED_GROUP_ID = -1; #ifdef BROKEN_SETREGID int setregid(rgid, egid) rb_gid_t rgid, egid; { if (rgid != -1 && rgid != getgid()) { if (egid == -1) egid = getegid(); if (setgid(rgid) < 0) return -1; } if (egid != -1 && egid != getegid()) { if (setegid(egid) < 0) return -1; } return 0; } #endif /* * call-seq: * Process::GID.change_privilege(integer) => fixnum * * Change the current process's real and effective group ID to that * specified by _integer_. Returns the new group ID. Not * available on all platforms. * * [Process.gid, Process.egid] #=> [0, 0] * Process::GID.change_privilege(33) #=> 33 * [Process.gid, Process.egid] #=> [33, 33] */ static VALUE p_gid_change_privilege(obj, id) VALUE obj, id; { int gid; check_gid_switch(); gid = NUM2INT(id); if (geteuid() == 0) { /* root-user */ #if defined(HAVE_SETRESGID) if (setresgid(gid, gid, gid) < 0) rb_sys_fail(0); SAVED_GROUP_ID = gid; #elif defined HAVE_SETGID if (setgid(gid) < 0) rb_sys_fail(0); SAVED_GROUP_ID = gid; #elif defined(HAVE_SETREGID) && !defined(OBSOLETE_SETREGID) if (getgid() == gid) { if (SAVED_GROUP_ID == gid) { if (setregid(-1, gid) < 0) rb_sys_fail(0); } else { if (gid == 0) { /* (r,e,s) == (root, y, x) */ if (setregid(-1, SAVED_GROUP_ID) < 0) rb_sys_fail(0); if (setregid(SAVED_GROUP_ID, 0) < 0) rb_sys_fail(0); SAVED_GROUP_ID = 0; /* (r,e,s) == (x, root, root) */ if (setregid(gid, gid) < 0) rb_sys_fail(0); SAVED_GROUP_ID = gid; } else { /* (r,e,s) == (z, y, x) */ if (setregid(0, 0) < 0) rb_sys_fail(0); SAVED_GROUP_ID = 0; if (setregid(gid, gid) < 0) rb_sys_fail(0); SAVED_GROUP_ID = gid; } } } else { if (setregid(gid, gid) < 0) rb_sys_fail(0); SAVED_GROUP_ID = gid; } #elif defined(HAVE_SETRGID) && defined (HAVE_SETEGID) if (getgid() == gid) { if (SAVED_GROUP_ID == gid) { if (setegid(gid) < 0) rb_sys_fail(0); } else { if (gid == 0) { if (setegid(gid) < 0) rb_sys_fail(0); if (setrgid(SAVED_GROUP_ID) < 0) rb_sys_fail(0); SAVED_GROUP_ID = 0; if (setrgid(0) < 0) rb_sys_fail(0); } else { if (setrgid(0) < 0) rb_sys_fail(0); SAVED_GROUP_ID = 0; if (setegid(gid) < 0) rb_sys_fail(0); if (setrgid(gid) < 0) rb_sys_fail(0); SAVED_GROUP_ID = gid; } } } else { if (setegid(gid) < 0) rb_sys_fail(0); if (setrgid(gid) < 0) rb_sys_fail(0); SAVED_GROUP_ID = gid; } #else rb_notimplement(); #endif } else { /* unprivileged user */ #if defined(HAVE_SETRESGID) if (setresgid((getgid() == gid)? -1: gid, (getegid() == gid)? -1: gid, (SAVED_GROUP_ID == gid)? -1: gid) < 0) rb_sys_fail(0); SAVED_GROUP_ID = gid; #elif defined(HAVE_SETREGID) && !defined(OBSOLETE_SETREGID) if (SAVED_GROUP_ID == gid) { if (setregid((getgid() == gid)? -1: gid, (getegid() == gid)? -1: gid) < 0) rb_sys_fail(0); } else if (getgid() != gid) { if (setregid(gid, (getegid() == gid)? -1: gid) < 0) rb_sys_fail(0); SAVED_GROUP_ID = gid; } else if (/* getgid() == gid && */ getegid() != gid) { if (setregid(getegid(), gid) < 0) rb_sys_fail(0); SAVED_GROUP_ID = gid; if (setregid(gid, -1) < 0) rb_sys_fail(0); } else { /* getgid() == gid && getegid() == gid */ if (setregid(-1, SAVED_GROUP_ID) < 0) rb_sys_fail(0); if (setregid(SAVED_GROUP_ID, gid) < 0) rb_sys_fail(0); SAVED_GROUP_ID = gid; if (setregid(gid, -1) < 0) rb_sys_fail(0); } #elif defined(HAVE_SETRGID) && defined(HAVE_SETEGID) if (SAVED_GROUP_ID == gid) { if (getegid() != gid && setegid(gid) < 0) rb_sys_fail(0); if (getgid() != gid && setrgid(gid) < 0) rb_sys_fail(0); } else if (/* SAVED_GROUP_ID != gid && */ getegid() == gid) { if (getgid() != gid) { if (setrgid(gid) < 0) rb_sys_fail(0); SAVED_GROUP_ID = gid; } else { if (setrgid(SAVED_GROUP_ID) < 0) rb_sys_fail(0); SAVED_GROUP_ID = gid; if (setrgid(gid) < 0) rb_sys_fail(0); } } else if (/* getegid() != gid && */ getgid() == gid) { if (setegid(gid) < 0) rb_sys_fail(0); if (setrgid(SAVED_GROUP_ID) < 0) rb_sys_fail(0); SAVED_GROUP_ID = gid; if (setrgid(gid) < 0) rb_sys_fail(0); } else { errno = EPERM; rb_sys_fail(0); } #elif defined HAVE_44BSD_SETGID if (getgid() == gid) { /* (r,e,s)==(gid,?,?) ==> (gid,gid,gid) */ if (setgid(gid) < 0) rb_sys_fail(0); SAVED_GROUP_ID = gid; } else { errno = EPERM; rb_sys_fail(0); } #elif defined HAVE_SETEGID if (getgid() == gid && SAVED_GROUP_ID == gid) { if (setegid(gid) < 0) rb_sys_fail(0); } else { errno = EPERM; rb_sys_fail(0); } #elif defined HAVE_SETGID if (getgid() == gid && SAVED_GROUP_ID == gid) { if (setgid(gid) < 0) rb_sys_fail(0); } else { errno = EPERM; rb_sys_fail(0); } #else rb_notimplement(); #endif } return INT2FIX(gid); } /* * call-seq: * Process.euid => fixnum * Process::UID.eid => fixnum * Process::Sys.geteuid => fixnum * * Returns the effective user ID for this process. * * Process.euid #=> 501 */ static VALUE proc_geteuid(obj) VALUE obj; { int euid = geteuid(); return INT2FIX(euid); } /* * call-seq: * Process.euid= integer * * Sets the effective user ID for this process. Not available on all * platforms. */ static VALUE proc_seteuid(obj, euid) VALUE obj, euid; { check_uid_switch(); #if defined(HAVE_SETRESUID) && !defined(__CHECKER__) if (setresuid(-1, NUM2INT(euid), -1) < 0) rb_sys_fail(0); #elif defined HAVE_SETREUID if (setreuid(-1, NUM2INT(euid)) < 0) rb_sys_fail(0); #elif defined HAVE_SETEUID if (seteuid(NUM2INT(euid)) < 0) rb_sys_fail(0); #elif defined HAVE_SETUID euid = NUM2INT(euid); if (euid == getuid()) { if (setuid(euid) < 0) rb_sys_fail(0); } else { rb_notimplement(); } #else rb_notimplement(); #endif return euid; } static VALUE rb_seteuid_core(euid) int euid; { int uid; check_uid_switch(); uid = getuid(); #if defined(HAVE_SETRESUID) && !defined(__CHECKER__) if (uid != euid) { if (setresuid(-1,euid,euid) < 0) rb_sys_fail(0); SAVED_USER_ID = euid; } else { if (setresuid(-1,euid,-1) < 0) rb_sys_fail(0); } #elif defined(HAVE_SETREUID) && !defined(OBSOLETE_SETREUID) if (setreuid(-1, euid) < 0) rb_sys_fail(0); if (uid != euid) { if (setreuid(euid,uid) < 0) rb_sys_fail(0); if (setreuid(uid,euid) < 0) rb_sys_fail(0); SAVED_USER_ID = euid; } #elif defined HAVE_SETEUID if (seteuid(euid) < 0) rb_sys_fail(0); #elif defined HAVE_SETUID if (geteuid() == 0) rb_sys_fail(0); if (setuid(euid) < 0) rb_sys_fail(0); #else rb_notimplement(); #endif return INT2FIX(euid); } /* * call-seq: * Process::UID.grant_privilege(integer) => fixnum * Process::UID.eid= integer => fixnum * * Set the effective user ID, and if possible, the saved user ID of * the process to the given _integer_. Returns the new * effective user ID. Not available on all platforms. * * [Process.uid, Process.euid] #=> [0, 0] * Process::UID.grant_privilege(31) #=> 31 * [Process.uid, Process.euid] #=> [0, 31] */ static VALUE p_uid_grant_privilege(obj, id) VALUE obj, id; { return rb_seteuid_core(NUM2INT(id)); } /* * call-seq: * Process.egid => fixnum * Process::GID.eid => fixnum * Process::Sys.geteid => fixnum * * Returns the effective group ID for this process. Not available on * all platforms. * * Process.egid #=> 500 */ static VALUE proc_getegid(obj) VALUE obj; { int egid = getegid(); return INT2FIX(egid); } /* * call-seq: * Process.egid = fixnum => fixnum * * Sets the effective group ID for this process. Not available on all * platforms. */ static VALUE proc_setegid(obj, egid) VALUE obj, egid; { check_gid_switch(); #if defined(HAVE_SETRESGID) && !defined(__CHECKER__) if (setresgid(-1, NUM2INT(egid), -1) < 0) rb_sys_fail(0); #elif defined HAVE_SETREGID if (setregid(-1, NUM2INT(egid)) < 0) rb_sys_fail(0); #elif defined HAVE_SETEGID if (setegid(NUM2INT(egid)) < 0) rb_sys_fail(0); #elif defined HAVE_SETGID egid = NUM2INT(egid); if (egid == getgid()) { if (setgid(egid) < 0) rb_sys_fail(0); } else { rb_notimplement(); } #else rb_notimplement(); #endif return egid; } static VALUE rb_setegid_core(egid) int egid; { int gid; check_gid_switch(); gid = getgid(); #if defined(HAVE_SETRESGID) && !defined(__CHECKER__) if (gid != egid) { if (setresgid(-1,egid,egid) < 0) rb_sys_fail(0); SAVED_GROUP_ID = egid; } else { if (setresgid(-1,egid,-1) < 0) rb_sys_fail(0); } #elif defined(HAVE_SETREGID) && !defined(OBSOLETE_SETREGID) if (setregid(-1, egid) < 0) rb_sys_fail(0); if (gid != egid) { if (setregid(egid,gid) < 0) rb_sys_fail(0); if (setregid(gid,egid) < 0) rb_sys_fail(0); SAVED_GROUP_ID = egid; } #elif defined HAVE_SETEGID if (setegid(egid) < 0) rb_sys_fail(0); #elif defined HAVE_SETGID if (geteuid() == 0 /* root user */) rb_sys_fail(0); if (setgid(egid) < 0) rb_sys_fail(0); #else rb_notimplement(); #endif return INT2FIX(egid); } /* * call-seq: * Process::GID.grant_privilege(integer) => fixnum * Process::GID.eid = integer => fixnum * * Set the effective group ID, and if possible, the saved group ID of * the process to the given _integer_. Returns the new * effective group ID. Not available on all platforms. * * [Process.gid, Process.egid] #=> [0, 0] * Process::GID.grant_privilege(31) #=> 33 * [Process.gid, Process.egid] #=> [0, 33] */ static VALUE p_gid_grant_privilege(obj, id) VALUE obj, id; { return rb_setegid_core(NUM2INT(id)); } /* * call-seq: * Process::UID.re_exchangeable? => true or false * * Returns +true+ if the real and effective user IDs of a * process may be exchanged on the current platform. * */ static VALUE p_uid_exchangeable() { #if defined(HAVE_SETRESUID) && !defined(__CHECKER__) return Qtrue; #elif defined(HAVE_SETREUID) && !defined(OBSOLETE_SETREUID) return Qtrue; #else return Qfalse; #endif } /* * call-seq: * Process::UID.re_exchange => fixnum * * Exchange real and effective user IDs and return the new effective * user ID. Not available on all platforms. * * [Process.uid, Process.euid] #=> [0, 31] * Process::UID.re_exchange #=> 0 * [Process.uid, Process.euid] #=> [31, 0] */ static VALUE p_uid_exchange(obj) VALUE obj; { int uid, euid; check_uid_switch(); uid = getuid(); euid = geteuid(); #if defined(HAVE_SETRESUID) && !defined(__CHECKER__) if (setresuid(euid, uid, uid) < 0) rb_sys_fail(0); SAVED_USER_ID = uid; #elif defined(HAVE_SETREUID) && !defined(OBSOLETE_SETREUID) if (setreuid(euid,uid) < 0) rb_sys_fail(0); SAVED_USER_ID = uid; #else rb_notimplement(); #endif return INT2FIX(uid); } /* * call-seq: * Process::GID.re_exchangeable? => true or false * * Returns +true+ if the real and effective group IDs of a * process may be exchanged on the current platform. * */ static VALUE p_gid_exchangeable() { #if defined(HAVE_SETRESGID) && !defined(__CHECKER__) return Qtrue; #elif defined(HAVE_SETREGID) && !defined(OBSOLETE_SETREGID) return Qtrue; #else return Qfalse; #endif } /* * call-seq: * Process::GID.re_exchange => fixnum * * Exchange real and effective group IDs and return the new effective * group ID. Not available on all platforms. * * [Process.gid, Process.egid] #=> [0, 33] * Process::GID.re_exchange #=> 0 * [Process.gid, Process.egid] #=> [33, 0] */ static VALUE p_gid_exchange(obj) VALUE obj; { int gid, egid; check_gid_switch(); gid = getgid(); egid = getegid(); #if defined(HAVE_SETRESGID) && !defined(__CHECKER__) if (setresgid(egid, gid, gid) < 0) rb_sys_fail(0); SAVED_GROUP_ID = gid; #elif defined(HAVE_SETREGID) && !defined(OBSOLETE_SETREGID) if (setregid(egid,gid) < 0) rb_sys_fail(0); SAVED_GROUP_ID = gid; #else rb_notimplement(); #endif return INT2FIX(gid); } /* [MG] :FIXME: Is this correct? I'm not sure how to phrase this. */ /* * call-seq: * Process::UID.sid_available? => true or false * * Returns +true+ if the current platform has saved user * ID functionality. * */ static VALUE p_uid_have_saved_id() { #if defined(HAVE_SETRESUID) || defined(HAVE_SETEUID) || defined(_POSIX_SAVED_IDS) return Qtrue; #else return Qfalse; #endif } #if defined(HAVE_SETRESUID) || defined(HAVE_SETEUID) || defined(_POSIX_SAVED_IDS) static VALUE p_uid_sw_ensure(id) int id; { under_uid_switch = 0; return rb_seteuid_core(id); } /* * call-seq: * Process::UID.switch => fixnum * Process::UID.switch {|| block} => object * * Switch the effective and real user IDs of the current process. If * a block is given, the user IDs will be switched back * after the block is executed. Returns the new effective user ID if * called without a block, and the return value of the block if one * is given. * */ static VALUE p_uid_switch(obj) VALUE obj; { int uid, euid; check_uid_switch(); uid = getuid(); euid = geteuid(); if (uid != euid) { proc_seteuid(obj, INT2FIX(uid)); if (rb_block_given_p()) { under_uid_switch = 1; return rb_ensure(rb_yield, Qnil, p_uid_sw_ensure, SAVED_USER_ID); } else { return INT2FIX(euid); } } else if (euid != SAVED_USER_ID) { proc_seteuid(obj, INT2FIX(SAVED_USER_ID)); if (rb_block_given_p()) { under_uid_switch = 1; return rb_ensure(rb_yield, Qnil, p_uid_sw_ensure, euid); } else { return INT2FIX(uid); } } else { errno = EPERM; rb_sys_fail(0); } #else static VALUE p_uid_sw_ensure(obj) VALUE obj; { under_uid_switch = 0; return p_uid_exchange(obj); } static VALUE p_uid_switch(obj) VALUE obj; { int uid, euid; check_uid_switch(); uid = getuid(); euid = geteuid(); if (uid == euid) { errno = EPERM; rb_sys_fail(0); } p_uid_exchange(obj); if (rb_block_given_p()) { under_uid_switch = 1; return rb_ensure(rb_yield, Qnil, p_uid_sw_ensure, obj); } else { return INT2FIX(euid); } #endif } /* [MG] :FIXME: Is this correct? I'm not sure how to phrase this. */ /* * call-seq: * Process::GID.sid_available? => true or false * * Returns +true+ if the current platform has saved group * ID functionality. * */ static VALUE p_gid_have_saved_id() { #if defined(HAVE_SETRESGID) || defined(HAVE_SETEGID) || defined(_POSIX_SAVED_IDS) return Qtrue; #else return Qfalse; #endif } #if defined(HAVE_SETRESGID) || defined(HAVE_SETEGID) || defined(_POSIX_SAVED_IDS) static VALUE p_gid_sw_ensure(id) int id; { under_gid_switch = 0; return rb_setegid_core(id); } /* * call-seq: * Process::GID.switch => fixnum * Process::GID.switch {|| block} => object * * Switch the effective and real group IDs of the current process. If * a block is given, the group IDs will be switched back * after the block is executed. Returns the new effective group ID if * called without a block, and the return value of the block if one * is given. * */ static VALUE p_gid_switch(obj) VALUE obj; { int gid, egid; check_gid_switch(); gid = getgid(); egid = getegid(); if (gid != egid) { proc_setegid(obj, INT2FIX(gid)); if (rb_block_given_p()) { under_gid_switch = 1; return rb_ensure(rb_yield, Qnil, p_gid_sw_ensure, SAVED_GROUP_ID); } else { return INT2FIX(egid); } } else if (egid != SAVED_GROUP_ID) { proc_setegid(obj, INT2FIX(SAVED_GROUP_ID)); if (rb_block_given_p()) { under_gid_switch = 1; return rb_ensure(rb_yield, Qnil, p_gid_sw_ensure, egid); } else { return INT2FIX(gid); } } else { errno = EPERM; rb_sys_fail(0); } #else static VALUE p_gid_sw_ensure(obj) VALUE obj; { under_gid_switch = 0; return p_gid_exchange(obj); } static VALUE p_gid_switch(obj) VALUE obj; { int gid, egid; check_gid_switch(); gid = getgid(); egid = getegid(); if (gid == egid) { errno = EPERM; rb_sys_fail(0); } p_gid_exchange(obj); if (rb_block_given_p()) { under_gid_switch = 1; return rb_ensure(rb_yield, Qnil, p_gid_sw_ensure, obj); } else { return INT2FIX(egid); } #endif } /* * call-seq: * Process.times => aStructTms * * Returns a Tms structure (see Struct::Tms * on page 388) that contains user and system CPU times for this * process. * * t = Process.times * [ t.utime, t.stime ] #=> [0.0, 0.02] */ VALUE rb_proc_times(obj) VALUE obj; { #if defined(HAVE_TIMES) && !defined(__CHECKER__) const double hertz = #ifdef HAVE__SC_CLK_TCK (double)sysconf(_SC_CLK_TCK); #else #ifndef HZ # ifdef CLK_TCK # define HZ CLK_TCK # else # define HZ 60 # endif #endif /* HZ */ HZ; #endif struct tms buf; volatile VALUE utime, stime, cutime, sctime; times(&buf); return rb_struct_new(S_Tms, utime = rb_float_new(buf.tms_utime / hertz), stime = rb_float_new(buf.tms_stime / hertz), cutime = rb_float_new(buf.tms_cutime / hertz), sctime = rb_float_new(buf.tms_cstime / hertz)); #else rb_notimplement(); #endif } VALUE rb_mProcess; VALUE rb_mProcUID; VALUE rb_mProcGID; VALUE rb_mProcID_Syscall; /* * The Process module is a collection of methods used to * manipulate processes. */ void Init_process() { rb_define_virtual_variable("$$", get_pid, 0); rb_define_readonly_variable("$?", &rb_last_status); rb_define_global_function("exec", rb_f_exec, -1); rb_define_global_function("fork", rb_f_fork, 0); rb_define_global_function("exit!", rb_f_exit_bang, -1); rb_define_global_function("system", rb_f_system, -1); rb_define_global_function("sleep", rb_f_sleep, -1); rb_mProcess = rb_define_module("Process"); #if !defined(_WIN32) && !defined(DJGPP) #ifdef WNOHANG rb_define_const(rb_mProcess, "WNOHANG", INT2FIX(WNOHANG)); #else rb_define_const(rb_mProcess, "WNOHANG", INT2FIX(0)); #endif #ifdef WUNTRACED rb_define_const(rb_mProcess, "WUNTRACED", INT2FIX(WUNTRACED)); #else rb_define_const(rb_mProcess, "WUNTRACED", INT2FIX(0)); #endif #endif rb_define_singleton_method(rb_mProcess, "fork", rb_f_fork, 0); rb_define_singleton_method(rb_mProcess, "exit!", rb_f_exit_bang, -1); rb_define_singleton_method(rb_mProcess, "exit", rb_f_exit, -1); /* in eval.c */ rb_define_singleton_method(rb_mProcess, "abort", rb_f_abort, -1); /* in eval.c */ rb_define_module_function(rb_mProcess, "kill", rb_f_kill, -1); /* in signal.c */ rb_define_module_function(rb_mProcess, "wait", proc_wait, -1); rb_define_module_function(rb_mProcess, "wait2", proc_wait2, -1); rb_define_module_function(rb_mProcess, "waitpid", proc_wait, -1); rb_define_module_function(rb_mProcess, "waitpid2", proc_wait2, -1); rb_define_module_function(rb_mProcess, "waitall", proc_waitall, 0); rb_define_module_function(rb_mProcess, "detach", proc_detach, 1); rb_cProcStatus = rb_define_class_under(rb_mProcess, "Status", rb_cObject); rb_undef_method(CLASS_OF(rb_cProcStatus), "new"); rb_define_method(rb_cProcStatus, "==", pst_equal, 1); rb_define_method(rb_cProcStatus, "&", pst_bitand, 1); rb_define_method(rb_cProcStatus, ">>", pst_rshift, 1); rb_define_method(rb_cProcStatus, "to_i", pst_to_i, 0); rb_define_method(rb_cProcStatus, "to_int", pst_to_i, 0); rb_define_method(rb_cProcStatus, "to_s", pst_to_s, 0); rb_define_method(rb_cProcStatus, "inspect", pst_inspect, 0); rb_define_method(rb_cProcStatus, "pid", pst_pid, 0); rb_define_method(rb_cProcStatus, "stopped?", pst_wifstopped, 0); rb_define_method(rb_cProcStatus, "stopsig", pst_wstopsig, 0); rb_define_method(rb_cProcStatus, "signaled?", pst_wifsignaled, 0); rb_define_method(rb_cProcStatus, "termsig", pst_wtermsig, 0); rb_define_method(rb_cProcStatus, "exited?", pst_wifexited, 0); rb_define_method(rb_cProcStatus, "exitstatus", pst_wexitstatus, 0); rb_define_method(rb_cProcStatus, "success?", pst_success_p, 0); rb_define_method(rb_cProcStatus, "coredump?", pst_wcoredump, 0); rb_define_module_function(rb_mProcess, "pid", get_pid, 0); rb_define_module_function(rb_mProcess, "ppid", get_ppid, 0); rb_define_module_function(rb_mProcess, "getpgrp", proc_getpgrp, 0); rb_define_module_function(rb_mProcess, "setpgrp", proc_setpgrp, 0); rb_define_module_function(rb_mProcess, "getpgid", proc_getpgid, 1); rb_define_module_function(rb_mProcess, "setpgid", proc_setpgid, 2); rb_define_module_function(rb_mProcess, "setsid", proc_setsid, 0); rb_define_module_function(rb_mProcess, "getpriority", proc_getpriority, 2); rb_define_module_function(rb_mProcess, "setpriority", proc_setpriority, 3); #ifdef HAVE_GETPRIORITY rb_define_const(rb_mProcess, "PRIO_PROCESS", INT2FIX(PRIO_PROCESS)); rb_define_const(rb_mProcess, "PRIO_PGRP", INT2FIX(PRIO_PGRP)); rb_define_const(rb_mProcess, "PRIO_USER", INT2FIX(PRIO_USER)); #endif rb_define_module_function(rb_mProcess, "getrlimit", proc_getrlimit, 1); rb_define_module_function(rb_mProcess, "setrlimit", proc_setrlimit, -1); #ifdef RLIM2NUM #ifdef RLIM_INFINITY rb_define_const(rb_mProcess, "RLIM_INFINITY", RLIM2NUM(RLIM_INFINITY)); #endif #ifdef RLIM_SAVED_MAX rb_define_const(rb_mProcess, "RLIM_SAVED_MAX", RLIM2NUM(RLIM_SAVED_MAX)); #endif #ifdef RLIM_SAVED_CUR rb_define_const(rb_mProcess, "RLIM_SAVED_CUR", RLIM2NUM(RLIM_SAVED_CUR)); #endif #ifdef RLIMIT_CORE rb_define_const(rb_mProcess, "RLIMIT_CORE", INT2FIX(RLIMIT_CORE)); #endif #ifdef RLIMIT_CPU rb_define_const(rb_mProcess, "RLIMIT_CPU", INT2FIX(RLIMIT_CPU)); #endif #ifdef RLIMIT_DATA rb_define_const(rb_mProcess, "RLIMIT_DATA", INT2FIX(RLIMIT_DATA)); #endif #ifdef RLIMIT_FSIZE rb_define_const(rb_mProcess, "RLIMIT_FSIZE", INT2FIX(RLIMIT_FSIZE)); #endif #ifdef RLIMIT_NOFILE rb_define_const(rb_mProcess, "RLIMIT_NOFILE", INT2FIX(RLIMIT_NOFILE)); #endif #ifdef RLIMIT_STACK rb_define_const(rb_mProcess, "RLIMIT_STACK", INT2FIX(RLIMIT_STACK)); #endif #ifdef RLIMIT_AS rb_define_const(rb_mProcess, "RLIMIT_AS", INT2FIX(RLIMIT_AS)); #endif #ifdef RLIMIT_MEMLOCK rb_define_const(rb_mProcess, "RLIMIT_MEMLOCK", INT2FIX(RLIMIT_MEMLOCK)); #endif #ifdef RLIMIT_NPROC rb_define_const(rb_mProcess, "RLIMIT_NPROC", INT2FIX(RLIMIT_NPROC)); #endif #ifdef RLIMIT_RSS rb_define_const(rb_mProcess, "RLIMIT_RSS", INT2FIX(RLIMIT_RSS)); #endif #ifdef RLIMIT_SBSIZE rb_define_const(rb_mProcess, "RLIMIT_SBSIZE", INT2FIX(RLIMIT_SBSIZE)); #endif #endif rb_define_module_function(rb_mProcess, "uid", proc_getuid, 0); rb_define_module_function(rb_mProcess, "uid=", proc_setuid, 1); rb_define_module_function(rb_mProcess, "gid", proc_getgid, 0); rb_define_module_function(rb_mProcess, "gid=", proc_setgid, 1); rb_define_module_function(rb_mProcess, "euid", proc_geteuid, 0); rb_define_module_function(rb_mProcess, "euid=", proc_seteuid, 1); rb_define_module_function(rb_mProcess, "egid", proc_getegid, 0); rb_define_module_function(rb_mProcess, "egid=", proc_setegid, 1); rb_define_module_function(rb_mProcess, "initgroups", proc_initgroups, 2); rb_define_module_function(rb_mProcess, "groups", proc_getgroups, 0); rb_define_module_function(rb_mProcess, "groups=", proc_setgroups, 1); rb_define_module_function(rb_mProcess, "maxgroups", proc_getmaxgroups, 0); rb_define_module_function(rb_mProcess, "maxgroups=", proc_setmaxgroups, 1); rb_define_module_function(rb_mProcess, "times", rb_proc_times, 0); #if defined(HAVE_TIMES) || defined(_WIN32) S_Tms = rb_struct_define("Tms", "utime", "stime", "cutime", "cstime", NULL); #endif SAVED_USER_ID = geteuid(); SAVED_GROUP_ID = getegid(); rb_mProcUID = rb_define_module_under(rb_mProcess, "UID"); rb_mProcGID = rb_define_module_under(rb_mProcess, "GID"); rb_define_module_function(rb_mProcUID, "rid", proc_getuid, 0); rb_define_module_function(rb_mProcGID, "rid", proc_getgid, 0); rb_define_module_function(rb_mProcUID, "eid", proc_geteuid, 0); rb_define_module_function(rb_mProcGID, "eid", proc_getegid, 0); rb_define_module_function(rb_mProcUID, "change_privilege", p_uid_change_privilege, 1); rb_define_module_function(rb_mProcGID, "change_privilege", p_gid_change_privilege, 1); rb_define_module_function(rb_mProcUID, "grant_privilege", p_uid_grant_privilege, 1); rb_define_module_function(rb_mProcGID, "grant_privilege", p_gid_grant_privilege, 1); rb_define_alias(rb_singleton_class(rb_mProcUID), "eid=", "grant_privilege"); rb_define_alias(rb_singleton_class(rb_mProcGID), "eid=", "grant_privilege"); rb_define_module_function(rb_mProcUID, "re_exchange", p_uid_exchange, 0); rb_define_module_function(rb_mProcGID, "re_exchange", p_gid_exchange, 0); rb_define_module_function(rb_mProcUID, "re_exchangeable?", p_uid_exchangeable, 0); rb_define_module_function(rb_mProcGID, "re_exchangeable?", p_gid_exchangeable, 0); rb_define_module_function(rb_mProcUID, "sid_available?", p_uid_have_saved_id, 0); rb_define_module_function(rb_mProcGID, "sid_available?", p_gid_have_saved_id, 0); rb_define_module_function(rb_mProcUID, "switch", p_uid_switch, 0); rb_define_module_function(rb_mProcGID, "switch", p_gid_switch, 0); rb_mProcID_Syscall = rb_define_module_under(rb_mProcess, "Sys"); rb_define_module_function(rb_mProcID_Syscall, "getuid", proc_getuid, 0); rb_define_module_function(rb_mProcID_Syscall, "geteuid", proc_geteuid, 0); rb_define_module_function(rb_mProcID_Syscall, "getgid", proc_getgid, 0); rb_define_module_function(rb_mProcID_Syscall, "getegid", proc_getegid, 0); rb_define_module_function(rb_mProcID_Syscall, "setuid", p_sys_setuid, 1); rb_define_module_function(rb_mProcID_Syscall, "setgid", p_sys_setgid, 1); rb_define_module_function(rb_mProcID_Syscall, "setruid", p_sys_setruid, 1); rb_define_module_function(rb_mProcID_Syscall, "setrgid", p_sys_setrgid, 1); rb_define_module_function(rb_mProcID_Syscall, "seteuid", p_sys_seteuid, 1); rb_define_module_function(rb_mProcID_Syscall, "setegid", p_sys_setegid, 1); rb_define_module_function(rb_mProcID_Syscall, "setreuid", p_sys_setreuid, 2); rb_define_module_function(rb_mProcID_Syscall, "setregid", p_sys_setregid, 2); rb_define_module_function(rb_mProcID_Syscall, "setresuid", p_sys_setresuid, 3); rb_define_module_function(rb_mProcID_Syscall, "setresgid", p_sys_setresgid, 3); rb_define_module_function(rb_mProcID_Syscall, "issetugid", p_sys_issetugid, 0); } /********************************************************************** random.c - $Author: shyouhei $ $Date: 2009-01-17 04:15:36 +0100 (Sat, 17 Jan 2009) $ created at: Fri Dec 24 16:39:21 JST 1993 Copyright (C) 1993-2003 Yukihiro Matsumoto **********************************************************************/ /* This is based on trimmed version of MT19937. To get the original version, contact . The original copyright notice follows. A C-program for MT19937, with initialization improved 2002/2/10. Coded by Takuji Nishimura and Makoto Matsumoto. This is a faster version by taking Shawn Cokus's optimization, Matthe Bellew's simplification, Isaku Wada's real version. Before using, initialize the state by using init_genrand(seed) or init_by_array(init_key, key_length). Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura, All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The names of its contributors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Any feedback is very welcome. http://www.math.keio.ac.jp/matumoto/emt.html email: matumoto@math.keio.ac.jp */ /* Period parameters */ #define N 624 #define M 397 #define MATRIX_A 0x9908b0dfUL /* constant vector a */ #define UMASK 0x80000000UL /* most significant w-r bits */ #define LMASK 0x7fffffffUL /* least significant r bits */ #define MIXBITS(u,v) ( ((u) & UMASK) | ((v) & LMASK) ) #define TWIST(u,v) ((MIXBITS(u,v) >> 1) ^ ((v)&1UL ? MATRIX_A : 0UL)) static unsigned long state[N]; /* the array for the state vector */ static int left = 1; static int initf = 0; static unsigned long *next; /* initializes state[N] with a seed */ static void init_genrand(s) unsigned long s; { int j; state[0]= s & 0xffffffffUL; for (j=1; j> 30)) + j); /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */ /* In the previous versions, MSBs of the seed affect */ /* only MSBs of the array state[]. */ /* 2002/01/09 modified by Makoto Matsumoto */ state[j] &= 0xffffffffUL; /* for >32 bit machines */ } left = 1; initf = 1; } /* initialize by an array with array-length */ /* init_key is the array for initializing keys */ /* key_length is its length */ /* slight change for C++, 2004/2/26 */ static void init_by_array(unsigned long init_key[], int key_length) { int i, j, k; init_genrand(19650218UL); i=1; j=0; k = (N>key_length ? N : key_length); for (; k; k--) { state[i] = (state[i] ^ ((state[i-1] ^ (state[i-1] >> 30)) * 1664525UL)) + init_key[j] + j; /* non linear */ state[i] &= 0xffffffffUL; /* for WORDSIZE > 32 machines */ i++; j++; if (i>=N) { state[0] = state[N-1]; i=1; } if (j>=key_length) j=0; } for (k=N-1; k; k--) { state[i] = (state[i] ^ ((state[i-1] ^ (state[i-1] >> 30)) * 1566083941UL)) - i; /* non linear */ state[i] &= 0xffffffffUL; /* for WORDSIZE > 32 machines */ i++; if (i>=N) { state[0] = state[N-1]; i=1; } } state[0] = 0x80000000UL; /* MSB is 1; assuring non-zero initial array */ left = 1; initf = 1; } static void next_state() { unsigned long *p=state; int j; /* if init_genrand() has not been called, */ /* a default initial seed is used */ if (initf==0) init_genrand(5489UL); left = N; next = state; for (j=N-M+1; --j; p++) *p = p[M] ^ TWIST(p[0], p[1]); for (j=M; --j; p++) *p = p[M-N] ^ TWIST(p[0], p[1]); *p = p[M-N] ^ TWIST(p[0], state[0]); } /* generates a random number on [0,0xffffffff]-interval */ static unsigned long genrand_int32(void) { unsigned long y; if (--left == 0) next_state(); y = *next++; /* Tempering */ y ^= (y >> 11); y ^= (y << 7) & 0x9d2c5680UL; y ^= (y << 15) & 0xefc60000UL; y ^= (y >> 18); return y; } /* generates a random number on [0,1) with 53-bit resolution*/ static double genrand_real(void) { unsigned long a=genrand_int32()>>5, b=genrand_int32()>>6; return(a*67108864.0+b)*(1.0/9007199254740992.0); } /* These real versions are due to Isaku Wada, 2002/01/09 added */ #undef N #undef M /* These real versions are due to Isaku Wada, 2002/01/09 added */ #include "ruby.h" #ifdef HAVE_UNISTD_H #include #endif #include #include #include #ifdef HAVE_FCNTL_H #include #endif static VALUE saved_seed = INT2FIX(0); static VALUE rand_init(vseed) VALUE vseed; { volatile VALUE seed; VALUE old; long len; unsigned long *buf; seed = rb_to_int(vseed); switch (TYPE(seed)) { case T_FIXNUM: len = sizeof(VALUE); break; case T_BIGNUM: len = RBIGNUM(seed)->len * SIZEOF_BDIGITS; if (len == 0) len = 4; break; default: rb_raise(rb_eTypeError, "failed to convert %s into Integer", rb_obj_classname(vseed)); } len = (len + 3) / 4; /* number of 32bit words */ buf = ALLOC_N(unsigned long, len); /* allocate longs for init_by_array */ memset(buf, 0, len * sizeof(long)); if (FIXNUM_P(seed)) { buf[0] = FIX2ULONG(seed) & 0xffffffff; #if SIZEOF_LONG > 4 buf[1] = FIX2ULONG(seed) >> 32; #endif } else { int i, j; for (i = RBIGNUM(seed)->len-1; 0 <= i; i--) { j = i * SIZEOF_BDIGITS / 4; #if SIZEOF_BDIGITS < 4 buf[j] <<= SIZEOF_BDIGITS * 8; #endif buf[j] |= ((BDIGIT *)RBIGNUM(seed)->digits)[i]; } } while (1 < len && buf[len-1] == 0) { len--; } if (len <= 1) { init_genrand(buf[0]); } else { if (buf[len-1] == 1) /* remove leading-zero-guard */ len--; init_by_array(buf, len); } old = saved_seed; saved_seed = seed; free(buf); return old; } static VALUE random_seed() { static int n = 0; struct timeval tv; int fd; struct stat statbuf; int seed_len; BDIGIT *digits; unsigned long *seed; NEWOBJ(big, struct RBignum); OBJSETUP(big, rb_cBignum, T_BIGNUM); seed_len = 4 * sizeof(long); big->sign = 1; big->len = seed_len / SIZEOF_BDIGITS + 1; digits = big->digits = ALLOC_N(BDIGIT, big->len); seed = (unsigned long *)big->digits; memset(digits, 0, big->len * SIZEOF_BDIGITS); #ifdef S_ISCHR if ((fd = open("/dev/urandom", O_RDONLY #ifdef O_NONBLOCK |O_NONBLOCK #endif #ifdef O_NOCTTY |O_NOCTTY #endif #ifdef O_NOFOLLOW |O_NOFOLLOW #endif )) >= 0) { if (fstat(fd, &statbuf) == 0 && S_ISCHR(statbuf.st_mode)) { read(fd, seed, seed_len); } close(fd); } #endif gettimeofday(&tv, 0); seed[0] ^= tv.tv_usec; seed[1] ^= tv.tv_sec; seed[2] ^= getpid() ^ (n++ << 16); seed[3] ^= (unsigned long)&seed; /* set leading-zero-guard if need. */ digits[big->len-1] = digits[big->len-2] <= 1 ? 1 : 0; return rb_big_norm((VALUE)big); } /* * call-seq: * srand(number=0) => old_seed * * Seeds the pseudorandom number generator to the value of * number.to_i.abs. If number is omitted, * seeds the generator using a combination of the time, the * process id, and a sequence number. (This is also the behavior if * Kernel::rand is called without previously calling * srand, but without the sequence.) By setting the seed * to a known value, scripts can be made deterministic during testing. * The previous seed value is returned. Also see Kernel::rand. */ static VALUE rb_f_srand(argc, argv, obj) int argc; VALUE *argv; VALUE obj; { VALUE seed, old; rb_secure(4); if (rb_scan_args(argc, argv, "01", &seed) == 0) { seed = random_seed(); } old = rand_init(seed); return old; } static unsigned long make_mask(unsigned long x) { x = x | x >> 1; x = x | x >> 2; x = x | x >> 4; x = x | x >> 8; x = x | x >> 16; #if 4 < SIZEOF_LONG x = x | x >> 32; #endif return x; } static unsigned long limited_rand(unsigned long limit) { unsigned long mask = make_mask(limit); int i; unsigned long val; retry: val = 0; for (i = SIZEOF_LONG/4-1; 0 <= i; i--) { if (mask >> (i * 32)) { val |= genrand_int32() << (i * 32); val &= mask; if (limit < val) goto retry; } } return val; } static VALUE limited_big_rand(struct RBignum *limit) { unsigned long mask, lim, rnd; struct RBignum *val; int i, len, boundary; len = (limit->len * SIZEOF_BDIGITS + 3) / 4; val = (struct RBignum *)rb_big_clone((VALUE)limit); val->sign = 1; #if SIZEOF_BDIGITS == 2 # define BIG_GET32(big,i) (((BDIGIT *)(big)->digits)[(i)*2] | \ ((i)*2+1 < (big)->len ? (((BDIGIT *)(big)->digits)[(i)*2+1] << 16) \ : 0)) # define BIG_SET32(big,i,d) ((((BDIGIT *)(big)->digits)[(i)*2] = (d) & 0xffff), \ ((i)*2+1 < (big)->len ? (((BDIGIT *)(big)->digits)[(i)*2+1] = (d) >> 16) \ : 0)) #else /* SIZEOF_BDIGITS == 4 */ # define BIG_GET32(big,i) (((BDIGIT *)(big)->digits)[i]) # define BIG_SET32(big,i,d) (((BDIGIT *)(big)->digits)[i] = (d)) #endif retry: mask = 0; boundary = 1; for (i = len-1; 0 <= i; i--) { lim = BIG_GET32(limit, i); mask = mask ? 0xffffffff : make_mask(lim); if (mask) { rnd = genrand_int32() & mask; if (boundary) { if (lim < rnd) goto retry; if (rnd < lim) boundary = 0; } } else { rnd = 0; } BIG_SET32(val, i, rnd); } return rb_big_norm((VALUE)val); } /* * call-seq: * rand(max=0) => number * * Converts max to an integer using max1 = * max.to_i.abs. If the result is zero, returns a * pseudorandom floating point number greater than or equal to 0.0 and * less than 1.0. Otherwise, returns a pseudorandom integer greater * than or equal to zero and less than max1. Kernel::srand * may be used to ensure repeatable sequences of random numbers between * different runs of the program. Ruby currently uses a modified * Mersenne Twister with a period of 2**19937-1. * * srand 1234 #=> 0 * [ rand, rand ] #=> [0.191519450163469, 0.49766366626136] * [ rand(10), rand(1000) ] #=> [6, 817] * srand 1234 #=> 1234 * [ rand, rand ] #=> [0.191519450163469, 0.49766366626136] */ static VALUE rb_f_rand(argc, argv, obj) int argc; VALUE *argv; VALUE obj; { VALUE vmax; long val, max; rb_scan_args(argc, argv, "01", &vmax); switch (TYPE(vmax)) { case T_FLOAT: if (RFLOAT(vmax)->value <= LONG_MAX && RFLOAT(vmax)->value >= LONG_MIN) { max = (long)RFLOAT(vmax)->value; break; } if (RFLOAT(vmax)->value < 0) vmax = rb_dbl2big(-RFLOAT(vmax)->value); else vmax = rb_dbl2big(RFLOAT(vmax)->value); /* fall through */ case T_BIGNUM: bignum: { struct RBignum *limit = (struct RBignum *)vmax; if (!limit->sign) { limit = (struct RBignum *)rb_big_clone(vmax); limit->sign = 1; } limit = (struct RBignum *)rb_big_minus((VALUE)limit, INT2FIX(1)); if (FIXNUM_P((VALUE)limit)) { if (FIX2LONG((VALUE)limit) == -1) return rb_float_new(genrand_real()); return LONG2NUM(limited_rand(FIX2LONG((VALUE)limit))); } return limited_big_rand(limit); } case T_NIL: max = 0; break; default: vmax = rb_Integer(vmax); if (TYPE(vmax) == T_BIGNUM) goto bignum; /* fall through */ case T_FIXNUM: max = FIX2LONG(vmax); break; } if (max == 0) { return rb_float_new(genrand_real()); } if (max < 0) max = -max; val = limited_rand(max-1); return LONG2NUM(val); } void Init_Random() { rand_init(random_seed()); rb_define_global_function("srand", rb_f_srand, -1); rb_define_global_function("rand", rb_f_rand, -1); rb_global_variable(&saved_seed); } /********************************************************************** range.c - $Author: shyouhei $ $Date: 2007-09-16 21:38:27 +0200 (Sun, 16 Sep 2007) $ created at: Thu Aug 19 17:46:47 JST 1993 Copyright (C) 1993-2003 Yukihiro Matsumoto **********************************************************************/ #include "ruby.h" VALUE rb_cRange; static ID id_cmp, id_succ, id_beg, id_end, id_excl; #define EXCL(r) RTEST(rb_ivar_get((r), id_excl)) #define SET_EXCL(r,v) rb_ivar_set((r), id_excl, (v) ? Qtrue : Qfalse) static VALUE range_failed() { rb_raise(rb_eArgError, "bad value for range"); return Qnil; /* dummy */ } static VALUE range_check(args) VALUE *args; { return rb_funcall(args[0], id_cmp, 1, args[1]); } static void range_init(range, beg, end, exclude_end) VALUE range, beg, end; int exclude_end; { VALUE args[2]; args[0] = beg; args[1] = end; if (!FIXNUM_P(beg) || !FIXNUM_P(end)) { VALUE v; v = rb_rescue(range_check, (VALUE)args, range_failed, 0); if (NIL_P(v)) range_failed(); } SET_EXCL(range, exclude_end); rb_ivar_set(range, id_beg, beg); rb_ivar_set(range, id_end, end); } VALUE rb_range_new(beg, end, exclude_end) VALUE beg, end; int exclude_end; { VALUE range = rb_obj_alloc(rb_cRange); range_init(range, beg, end, exclude_end); return range; } /* * call-seq: * Range.new(start, end, exclusive=false) => range * * Constructs a range using the given start and end. If the third * parameter is omitted or is false, the range will include * the end object; otherwise, it will be excluded. */ static VALUE range_initialize(argc, argv, range) int argc; VALUE *argv; VALUE range; { VALUE beg, end, flags; rb_scan_args(argc, argv, "21", &beg, &end, &flags); /* Ranges are immutable, so that they should be initialized only once. */ if (rb_ivar_defined(range, id_beg)) { rb_name_error(rb_intern("initialize"), "`initialize' called twice"); } range_init(range, beg, end, RTEST(flags)); return Qnil; } /* * call-seq: * rng.exclude_end? => true or false * * Returns true if rng excludes its end value. */ static VALUE range_exclude_end_p(range) VALUE range; { return EXCL(range) ? Qtrue : Qfalse; } /* * call-seq: * rng == obj => true or false * * Returns true only if obj is a Range, has equivalent * beginning and end items (by comparing them with ==), and has * the same #exclude_end? setting as rng. * * (0..2) == (0..2) #=> true * (0..2) == Range.new(0,2) #=> true * (0..2) == (0...2) #=> false * */ static VALUE range_eq(range, obj) VALUE range, obj; { if (range == obj) return Qtrue; if (!rb_obj_is_instance_of(obj, rb_obj_class(range))) return Qfalse; if (!rb_equal(rb_ivar_get(range, id_beg), rb_ivar_get(obj, id_beg))) return Qfalse; if (!rb_equal(rb_ivar_get(range, id_end), rb_ivar_get(obj, id_end))) return Qfalse; if (EXCL(range) != EXCL(obj)) return Qfalse; return Qtrue; } static int r_lt(a, b) VALUE a, b; { VALUE r = rb_funcall(a, id_cmp, 1, b); if (NIL_P(r)) return Qfalse; if (rb_cmpint(r, a, b) < 0) return Qtrue; return Qfalse; } static int r_le(a, b) VALUE a, b; { int c; VALUE r = rb_funcall(a, id_cmp, 1, b); if (NIL_P(r)) return Qfalse; c = rb_cmpint(r, a, b); if (c == 0) return INT2FIX(0); if (c < 0) return Qtrue; return Qfalse; } /* * call-seq: * rng.eql?(obj) => true or false * * Returns true only if obj is a Range, has equivalent * beginning and end items (by comparing them with #eql?), and has the same * #exclude_end? setting as rng. * * (0..2) == (0..2) #=> true * (0..2) == Range.new(0,2) #=> true * (0..2) == (0...2) #=> false * */ static VALUE range_eql(range, obj) VALUE range, obj; { if (range == obj) return Qtrue; if (!rb_obj_is_instance_of(obj, rb_obj_class(range))) return Qfalse; if (!rb_eql(rb_ivar_get(range, id_beg), rb_ivar_get(obj, id_beg))) return Qfalse; if (!rb_eql(rb_ivar_get(range, id_end), rb_ivar_get(obj, id_end))) return Qfalse; if (EXCL(range) != EXCL(obj)) return Qfalse; return Qtrue; } /* * call-seq: * rng.hash => fixnum * * Generate a hash value such that two ranges with the same start and * end points, and the same value for the "exclude end" flag, generate * the same hash value. */ static VALUE range_hash(range) VALUE range; { long hash = EXCL(range); VALUE v; v = rb_hash(rb_ivar_get(range, id_beg)); hash ^= v << 1; v = rb_hash(rb_ivar_get(range, id_end)); hash ^= v << 9; hash ^= EXCL(range) << 24; return LONG2FIX(hash); } static VALUE str_step(args) VALUE *args; { return rb_str_upto(args[0], args[1], EXCL(args[2])); } static void range_each_func(range, func, v, e, arg) VALUE range; void (*func) _((VALUE, void*)); VALUE v, e; void *arg; { int c; if (EXCL(range)) { while (r_lt(v, e)) { (*func)(v, arg); v = rb_funcall(v, id_succ, 0, 0); } } else { while (RTEST(c = r_le(v, e))) { (*func)(v, arg); if (c == INT2FIX(0)) break; v = rb_funcall(v, id_succ, 0, 0); } } } static VALUE step_i(i, iter) VALUE i; long *iter; { iter[0]--; if (iter[0] == 0) { rb_yield(i); iter[0] = iter[1]; } return Qnil; } /* * call-seq: * rng.step(n=1) {| obj | block } => rng * * Iterates over rng, passing each nth element to the block. If * the range contains numbers or strings, natural ordering is used. Otherwise * step invokes succ to iterate through range * elements. The following code uses class Xs, which is defined * in the class-level documentation. * * range = Xs.new(1)..Xs.new(10) * range.step(2) {|x| puts x} * range.step(3) {|x| puts x} * * produces: * * 1 x * 3 xxx * 5 xxxxx * 7 xxxxxxx * 9 xxxxxxxxx * 1 x * 4 xxxx * 7 xxxxxxx * 10 xxxxxxxxxx */ static VALUE range_step(argc, argv, range) int argc; VALUE *argv; VALUE range; { VALUE b, e, step; long unit; b = rb_ivar_get(range, id_beg); e = rb_ivar_get(range, id_end); if (rb_scan_args(argc, argv, "01", &step) == 0) { step = INT2FIX(1); } unit = NUM2LONG(step); if (unit < 0) { rb_raise(rb_eArgError, "step can't be negative"); } if (FIXNUM_P(b) && FIXNUM_P(e)) { /* fixnums are special */ long end = FIX2LONG(e); long i; if (unit == 0) rb_raise(rb_eArgError, "step can't be 0"); if (!EXCL(range)) end += 1; i = FIX2LONG(b); while (i < end) { rb_yield(LONG2NUM(i)); if (i + unit < i) break; i += unit; } } else { VALUE tmp = rb_check_string_type(b); if (!NIL_P(tmp)) { VALUE args[5]; long iter[2]; b = tmp; if (unit == 0) rb_raise(rb_eArgError, "step can't be 0"); args[0] = b; args[1] = e; args[2] = range; iter[0] = 1; iter[1] = unit; rb_iterate((VALUE(*)_((VALUE)))str_step, (VALUE)args, step_i, (VALUE)iter); } else if (rb_obj_is_kind_of(b, rb_cNumeric)) { ID c = rb_intern(EXCL(range) ? "<" : "<="); if (rb_equal(step, INT2FIX(0))) rb_raise(rb_eArgError, "step can't be 0"); while (RTEST(rb_funcall(b, c, 1, e))) { rb_yield(b); b = rb_funcall(b, '+', 1, step); } } else { long args[2]; if (unit == 0) rb_raise(rb_eArgError, "step can't be 0"); if (!rb_respond_to(b, id_succ)) { rb_raise(rb_eTypeError, "can't iterate from %s", rb_obj_classname(b)); } args[0] = 1; args[1] = unit; range_each_func(range, step_i, b, e, args); } } return range; } static void each_i(v, arg) VALUE v; void *arg; { rb_yield(v); } /* * call-seq: * rng.each {| i | block } => rng * * Iterates over the elements rng, passing each in turn to the * block. You can only iterate if the start object of the range * supports the +succ+ method (which means that you can't iterate over * ranges of +Float+ objects). * * (10..15).each do |n| * print n, ' ' * end * * produces: * * 10 11 12 13 14 15 */ static VALUE range_each(range) VALUE range; { VALUE beg, end; beg = rb_ivar_get(range, id_beg); end = rb_ivar_get(range, id_end); if (!rb_respond_to(beg, id_succ)) { rb_raise(rb_eTypeError, "can't iterate from %s", rb_obj_classname(beg)); } if (FIXNUM_P(beg) && FIXNUM_P(end)) { /* fixnums are special */ long lim = FIX2LONG(end); long i; if (!EXCL(range)) lim += 1; for (i=FIX2LONG(beg); i obj * rng.begin => obj * * Returns the first object in rng. */ static VALUE range_first(range) VALUE range; { return rb_ivar_get(range, id_beg); } /* * call-seq: * rng.end => obj * rng.last => obj * * Returns the object that defines the end of rng. * * (1..10).end #=> 10 * (1...10).end #=> 10 */ static VALUE range_last(range) VALUE range; { return rb_ivar_get(range, id_end); } VALUE rb_range_beg_len(range, begp, lenp, len, err) VALUE range; long *begp, *lenp; long len; int err; { long beg, end, b, e; if (!rb_obj_is_kind_of(range, rb_cRange)) return Qfalse; beg = b = NUM2LONG(rb_ivar_get(range, id_beg)); end = e = NUM2LONG(rb_ivar_get(range, id_end)); if (beg < 0) { beg += len; if (beg < 0) goto out_of_range; } if (err == 0 || err == 2) { if (beg > len) goto out_of_range; if (end > len) end = len; } if (end < 0) end += len; if (!EXCL(range)) end++; /* include end point */ len = end - beg; if (len < 0) len = 0; *begp = beg; *lenp = len; return Qtrue; out_of_range: if (err) { rb_raise(rb_eRangeError, "%ld..%s%ld out of range", b, EXCL(range)? "." : "", e); } return Qnil; } /* * call-seq: * rng.to_s => string * * Convert this range object to a printable form. */ static VALUE range_to_s(range) VALUE range; { VALUE str, str2; str = rb_obj_as_string(rb_ivar_get(range, id_beg)); str2 = rb_obj_as_string(rb_ivar_get(range, id_end)); str = rb_str_dup(str); rb_str_cat(str, "...", EXCL(range)?3:2); rb_str_append(str, str2); OBJ_INFECT(str, str2); return str; } /* * call-seq: * rng.inspect => string * * Convert this range object to a printable form (using * inspect to convert the start and end * objects). */ static VALUE range_inspect(range) VALUE range; { VALUE str, str2; str = rb_inspect(rb_ivar_get(range, id_beg)); str2 = rb_inspect(rb_ivar_get(range, id_end)); str = rb_str_dup(str); rb_str_cat(str, "...", EXCL(range)?3:2); rb_str_append(str, str2); OBJ_INFECT(str, str2); return str; } /* * call-seq: * rng === obj => true or false * rng.member?(val) => true or false * rng.include?(val) => true or false * * Returns true if obj is an element of * rng, false otherwise. Conveniently, * === is the comparison operator used by * case statements. * * case 79 * when 1..50 then print "low\n" * when 51..75 then print "medium\n" * when 76..100 then print "high\n" * end * * produces: * * high */ static VALUE range_include(range, val) VALUE range, val; { VALUE beg, end; beg = rb_ivar_get(range, id_beg); end = rb_ivar_get(range, id_end); if (r_le(beg, val)) { if (EXCL(range)) { if (r_lt(val, end)) return Qtrue; } else { if (r_le(val, end)) return Qtrue; } } return Qfalse; } /* A Range represents an interval---a set of values with a * start and an end. Ranges may be constructed using the * s..e and * s...e literals, or with * Range::new. Ranges constructed using .. * run from the start to the end inclusively. Those created using * ... exclude the end value. When used as an iterator, * ranges return each value in the sequence. * * (-1..-5).to_a #=> [] * (-5..-1).to_a #=> [-5, -4, -3, -2, -1] * ('a'..'e').to_a #=> ["a", "b", "c", "d", "e"] * ('a'...'e').to_a #=> ["a", "b", "c", "d"] * * Ranges can be constructed using objects of any type, as long as the * objects can be compared using their <=> operator and * they support the succ method to return the next object * in sequence. * * class Xs # represent a string of 'x's * include Comparable * attr :length * def initialize(n) * @length = n * end * def succ * Xs.new(@length + 1) * end * def <=>(other) * @length <=> other.length * end * def to_s * sprintf "%2d #{inspect}", @length * end * def inspect * 'x' * @length * end * end * * r = Xs.new(3)..Xs.new(6) #=> xxx..xxxxxx * r.to_a #=> [xxx, xxxx, xxxxx, xxxxxx] * r.member?(Xs.new(5)) #=> true * * In the previous code example, class Xs includes the * Comparable module. This is because * Enumerable#member? checks for equality using * ==. Including Comparable ensures that the * == method is defined in terms of the <=> * method implemented in Xs. * */ void Init_Range() { rb_cRange = rb_define_class("Range", rb_cObject); rb_include_module(rb_cRange, rb_mEnumerable); rb_define_method(rb_cRange, "initialize", range_initialize, -1); rb_define_method(rb_cRange, "==", range_eq, 1); rb_define_method(rb_cRange, "===", range_include, 1); rb_define_method(rb_cRange, "eql?", range_eql, 1); rb_define_method(rb_cRange, "hash", range_hash, 0); rb_define_method(rb_cRange, "each", range_each, 0); rb_define_method(rb_cRange, "step", range_step, -1); rb_define_method(rb_cRange, "first", range_first, 0); rb_define_method(rb_cRange, "last", range_last, 0); rb_define_method(rb_cRange, "begin", range_first, 0); rb_define_method(rb_cRange, "end", range_last, 0); rb_define_method(rb_cRange, "to_s", range_to_s, 0); rb_define_method(rb_cRange, "inspect", range_inspect, 0); rb_define_method(rb_cRange, "exclude_end?", range_exclude_end_p, 0); rb_define_method(rb_cRange, "member?", range_include, 1); rb_define_method(rb_cRange, "include?", range_include, 1); id_cmp = rb_intern("<=>"); id_succ = rb_intern("succ"); id_beg = rb_intern("begin"); id_end = rb_intern("end"); id_excl = rb_intern("excl"); } /********************************************************************** re.c - $Author: shyouhei $ created at: Mon Aug 9 18:24:49 JST 1993 Copyright (C) 1993-2003 Yukihiro Matsumoto **********************************************************************/ #include "ruby.h" #include "re.h" #include VALUE rb_eRegexpError; #define BEG(no) regs->beg[no] #define END(no) regs->end[no] #if 'a' == 97 /* it's ascii */ static const char casetable[] = { '\000', '\001', '\002', '\003', '\004', '\005', '\006', '\007', '\010', '\011', '\012', '\013', '\014', '\015', '\016', '\017', '\020', '\021', '\022', '\023', '\024', '\025', '\026', '\027', '\030', '\031', '\032', '\033', '\034', '\035', '\036', '\037', /* ' ' '!' '"' '#' '$' '%' '&' ''' */ '\040', '\041', '\042', '\043', '\044', '\045', '\046', '\047', /* '(' ')' '*' '+' ',' '-' '.' '/' */ '\050', '\051', '\052', '\053', '\054', '\055', '\056', '\057', /* '0' '1' '2' '3' '4' '5' '6' '7' */ '\060', '\061', '\062', '\063', '\064', '\065', '\066', '\067', /* '8' '9' ':' ';' '<' '=' '>' '?' */ '\070', '\071', '\072', '\073', '\074', '\075', '\076', '\077', /* '@' 'A' 'B' 'C' 'D' 'E' 'F' 'G' */ '\100', '\141', '\142', '\143', '\144', '\145', '\146', '\147', /* 'H' 'I' 'J' 'K' 'L' 'M' 'N' 'O' */ '\150', '\151', '\152', '\153', '\154', '\155', '\156', '\157', /* 'P' 'Q' 'R' 'S' 'T' 'U' 'V' 'W' */ '\160', '\161', '\162', '\163', '\164', '\165', '\166', '\167', /* 'X' 'Y' 'Z' '[' '\' ']' '^' '_' */ '\170', '\171', '\172', '\133', '\134', '\135', '\136', '\137', /* '`' 'a' 'b' 'c' 'd' 'e' 'f' 'g' */ '\140', '\141', '\142', '\143', '\144', '\145', '\146', '\147', /* 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' */ '\150', '\151', '\152', '\153', '\154', '\155', '\156', '\157', /* 'p' 'q' 'r' 's' 't' 'u' 'v' 'w' */ '\160', '\161', '\162', '\163', '\164', '\165', '\166', '\167', /* 'x' 'y' 'z' '{' '|' '}' '~' */ '\170', '\171', '\172', '\173', '\174', '\175', '\176', '\177', '\200', '\201', '\202', '\203', '\204', '\205', '\206', '\207', '\210', '\211', '\212', '\213', '\214', '\215', '\216', '\217', '\220', '\221', '\222', '\223', '\224', '\225', '\226', '\227', '\230', '\231', '\232', '\233', '\234', '\235', '\236', '\237', '\240', '\241', '\242', '\243', '\244', '\245', '\246', '\247', '\250', '\251', '\252', '\253', '\254', '\255', '\256', '\257', '\260', '\261', '\262', '\263', '\264', '\265', '\266', '\267', '\270', '\271', '\272', '\273', '\274', '\275', '\276', '\277', '\300', '\301', '\302', '\303', '\304', '\305', '\306', '\307', '\310', '\311', '\312', '\313', '\314', '\315', '\316', '\317', '\320', '\321', '\322', '\323', '\324', '\325', '\326', '\327', '\330', '\331', '\332', '\333', '\334', '\335', '\336', '\337', '\340', '\341', '\342', '\343', '\344', '\345', '\346', '\347', '\350', '\351', '\352', '\353', '\354', '\355', '\356', '\357', '\360', '\361', '\362', '\363', '\364', '\365', '\366', '\367', '\370', '\371', '\372', '\373', '\374', '\375', '\376', '\377', }; #else # error >>> "You lose. You will need a translation table for your character set." <<< #endif int rb_memcicmp(x, y, len) const void *x, *y; long len; { const unsigned char *p1 = x, *p2 = y; int tmp; while (len--) { if ((tmp = casetable[(unsigned)*p1++] - casetable[(unsigned)*p2++]) != 0) return tmp; } return 0; } int rb_memcmp(p1, p2, len) const void *p1, *p2; long len; { if (!ruby_ignorecase) { return memcmp(p1, p2, len); } return rb_memcicmp(p1, p2, len); } long rb_memsearch(x0, m, y0, n) const void *x0, *y0; long m, n; { const unsigned char *x = (unsigned char *)x0, *y = (unsigned char *)y0; const unsigned char *s, *e; long i; int d; unsigned long hx, hy; #define KR_REHASH(a, b, h) (((h) << 1) - (((unsigned long)(a))< n) return -1; s = y; e = s + n - m; /* Preprocessing */ /* computes d = 2^(m-1) with the left-shift operator */ d = sizeof(hx) * CHAR_BIT - 1; if (d > m) d = m; if (ruby_ignorecase) { if (n == m) { return rb_memcicmp(x, s, m) == 0 ? 0 : -1; } /* Prepare hash value */ for (hy = hx = i = 0; i < d; ++i) { hx = KR_REHASH(0, casetable[x[i]], hx); hy = KR_REHASH(0, casetable[s[i]], hy); } /* Searching */ while (hx != hy || rb_memcicmp(x, s, m)) { if (s >= e) return -1; hy = KR_REHASH(casetable[*s], casetable[*(s+d)], hy); s++; } } else { if (n == m) { return memcmp(x, s, m) == 0 ? 0 : -1; } /* Prepare hash value */ for (hy = hx = i = 0; i < d; ++i) { hx = KR_REHASH(0, x[i], hx); hy = KR_REHASH(0, s[i], hy); } /* Searching */ while (hx != hy || memcmp(x, s, m)) { if (s >= e) return -1; hy = KR_REHASH(*s, *(s+d), hy); s++; } } return s-y; } #define REG_LITERAL FL_USER5 #define REG_CASESTATE FL_USER0 #define KCODE_NONE 0 #define KCODE_EUC FL_USER1 #define KCODE_SJIS FL_USER2 #define KCODE_UTF8 FL_USER3 #define KCODE_FIXED FL_USER4 #define KCODE_MASK (KCODE_EUC|KCODE_SJIS|KCODE_UTF8) static int reg_kcode = DEFAULT_KCODE; static void kcode_euc(re) struct RRegexp *re; { FL_UNSET(re, KCODE_MASK); FL_SET(re, KCODE_EUC); FL_SET(re, KCODE_FIXED); } static void kcode_sjis(re) struct RRegexp *re; { FL_UNSET(re, KCODE_MASK); FL_SET(re, KCODE_SJIS); FL_SET(re, KCODE_FIXED); } static void kcode_utf8(re) struct RRegexp *re; { FL_UNSET(re, KCODE_MASK); FL_SET(re, KCODE_UTF8); FL_SET(re, KCODE_FIXED); } static void kcode_none(re) struct RRegexp *re; { FL_UNSET(re, KCODE_MASK); FL_SET(re, KCODE_FIXED); } static int curr_kcode; void rb_kcode_set_option(re) VALUE re; { if (!FL_TEST(re, KCODE_FIXED)) return; curr_kcode = RBASIC(re)->flags & KCODE_MASK; if (reg_kcode == curr_kcode) return; switch (curr_kcode) { case KCODE_NONE: re_mbcinit(MBCTYPE_ASCII); break; case KCODE_EUC: re_mbcinit(MBCTYPE_EUC); break; case KCODE_SJIS: re_mbcinit(MBCTYPE_SJIS); break; case KCODE_UTF8: re_mbcinit(MBCTYPE_UTF8); break; } } void rb_kcode_reset_option() { if (reg_kcode == curr_kcode) return; switch (reg_kcode) { case KCODE_NONE: re_mbcinit(MBCTYPE_ASCII); break; case KCODE_EUC: re_mbcinit(MBCTYPE_EUC); break; case KCODE_SJIS: re_mbcinit(MBCTYPE_SJIS); break; case KCODE_UTF8: re_mbcinit(MBCTYPE_UTF8); break; } } int rb_reg_mbclen2(c, re) unsigned int c; VALUE re; { int len; if (!FL_TEST(re, KCODE_FIXED)) return mbclen(c); rb_kcode_set_option(re); len = mbclen(c); rb_kcode_reset_option(); return len; } static void rb_reg_check(re) VALUE re; { if (!RREGEXP(re)->ptr || !RREGEXP(re)->str) { rb_raise(rb_eTypeError, "uninitialized Regexp"); } } extern int ruby_in_compile; static void rb_reg_expr_str(str, s, len) VALUE str; const char *s; long len; { const char *p, *pend; int need_escape = 0; p = s; pend = p + len; while (pptr->options & RE_OPTION_MULTILINE) rb_str_buf_cat2(str, "m"); if (RREGEXP(re)->ptr->options & RE_OPTION_IGNORECASE) rb_str_buf_cat2(str, "i"); if (RREGEXP(re)->ptr->options & RE_OPTION_EXTENDED) rb_str_buf_cat2(str, "x"); if (FL_TEST(re, KCODE_FIXED)) { switch ((RBASIC(re)->flags & KCODE_MASK)) { case KCODE_NONE: rb_str_buf_cat2(str, "n"); break; case KCODE_EUC: rb_str_buf_cat2(str, "e"); break; case KCODE_SJIS: rb_str_buf_cat2(str, "s"); break; case KCODE_UTF8: rb_str_buf_cat2(str, "u"); break; } } } OBJ_INFECT(str, re); return str; } /* * call-seq: * rxp.source => str * * Returns the original string of the pattern. * * /ab+c/ix.source #=> "ab+c" */ static VALUE rb_reg_source(re) VALUE re; { VALUE str; rb_reg_check(re); str = rb_str_new(RREGEXP(re)->str,RREGEXP(re)->len); if (OBJ_TAINTED(re)) OBJ_TAINT(str); return str; } /* * call-seq: * rxp.inspect => string * * Produce a nicely formatted string-version of _rxp_. Perhaps surprisingly, * #inspect actually produces the more natural version of * the string than #to_s. * * /ab+c/ix.to_s #=> /ab+c/ix */ static VALUE rb_reg_inspect(re) VALUE re; { rb_reg_check(re); return rb_reg_desc(RREGEXP(re)->str, RREGEXP(re)->len, re); } /* * call-seq: * rxp.to_s => str * * Returns a string containing the regular expression and its options (using the * (?xxx:yyy) notation. This string can be fed back in to * Regexp::new to a regular expression with the same semantics as * the original. (However, Regexp#== may not return true when * comparing the two, as the source of the regular expression itself may * differ, as the example shows). Regexp#inspect produces a * generally more readable version of rxp. * * r1 = /ab+c/ix #=> /ab+c/ix * s1 = r1.to_s #=> "(?ix-m:ab+c)" * r2 = Regexp.new(s1) #=> /(?ix-m:ab+c)/ * r1 == r2 #=> false * r1.source #=> "ab+c" * r2.source #=> "(?ix-m:ab+c)" */ static VALUE rb_reg_to_s(re) VALUE re; { int options; const int embeddable = RE_OPTION_MULTILINE|RE_OPTION_IGNORECASE|RE_OPTION_EXTENDED; long len; const char* ptr; VALUE str = rb_str_buf_new2("(?"); rb_reg_check(re); options = RREGEXP(re)->ptr->options; ptr = RREGEXP(re)->str; len = RREGEXP(re)->len; again: if (len >= 4 && ptr[0] == '(' && ptr[1] == '?') { int err = 1; ptr += 2; if ((len -= 2) > 0) { do { if (*ptr == 'm') { options |= RE_OPTION_MULTILINE; } else if (*ptr == 'i') { options |= RE_OPTION_IGNORECASE; } else if (*ptr == 'x') { options |= RE_OPTION_EXTENDED; } else break; ++ptr; } while (--len > 0); } if (len > 1 && *ptr == '-') { ++ptr; --len; do { if (*ptr == 'm') { options &= ~RE_OPTION_MULTILINE; } else if (*ptr == 'i') { options &= ~RE_OPTION_IGNORECASE; } else if (*ptr == 'x') { options &= ~RE_OPTION_EXTENDED; } else break; ++ptr; } while (--len > 0); } if (*ptr == ')') { --len; ++ptr; goto again; } if (*ptr == ':' && ptr[len-1] == ')') { Regexp *rp; rb_kcode_set_option(re); rp = ALLOC(Regexp); MEMZERO((char *)rp, Regexp, 1); err = re_compile_pattern(++ptr, len -= 2, rp) != 0; rb_kcode_reset_option(); re_free_pattern(rp); } if (err) { options = RREGEXP(re)->ptr->options; ptr = RREGEXP(re)->str; len = RREGEXP(re)->len; } } if (options & RE_OPTION_MULTILINE) rb_str_buf_cat2(str, "m"); if (options & RE_OPTION_IGNORECASE) rb_str_buf_cat2(str, "i"); if (options & RE_OPTION_EXTENDED) rb_str_buf_cat2(str, "x"); if ((options & embeddable) != embeddable) { rb_str_buf_cat2(str, "-"); if (!(options & RE_OPTION_MULTILINE)) rb_str_buf_cat2(str, "m"); if (!(options & RE_OPTION_IGNORECASE)) rb_str_buf_cat2(str, "i"); if (!(options & RE_OPTION_EXTENDED)) rb_str_buf_cat2(str, "x"); } rb_str_buf_cat2(str, ":"); rb_reg_expr_str(str, ptr, len); rb_str_buf_cat2(str, ")"); OBJ_INFECT(str, re); return str; } static void rb_reg_raise(s, len, err, re) const char *s; long len; const char *err; VALUE re; { VALUE desc = rb_reg_desc(s, len, re); if (ruby_in_compile) rb_compile_error("%s: %s", err, RSTRING(desc)->ptr); else rb_raise(rb_eRegexpError, "%s: %s", err, RSTRING(desc)->ptr); } /* * call-seq: * rxp.casefold? => true or false * * Returns the value of the case-insensitive flag. */ static VALUE rb_reg_casefold_p(re) VALUE re; { rb_reg_check(re); if (RREGEXP(re)->ptr->options & RE_OPTION_IGNORECASE) return Qtrue; return Qfalse; } /* * call-seq: * rxp.options => fixnum * * Returns the set of bits corresponding to the options used when creating this * Regexp (see Regexp::new for details. Note that additional bits * may be set in the returned options: these are used internally by the regular * expression code. These extra bits are ignored if the options are passed to * Regexp::new. * * Regexp::IGNORECASE #=> 1 * Regexp::EXTENDED #=> 2 * Regexp::MULTILINE #=> 4 * * /cat/.options #=> 128 * /cat/ix.options #=> 131 * Regexp.new('cat', true).options #=> 129 * Regexp.new('cat', 0, 's').options #=> 384 * * r = /cat/ix * Regexp.new(r.source, r.options) #=> /cat/ix */ static VALUE rb_reg_options_m(re) VALUE re; { int options = rb_reg_options(re); return INT2NUM(options); } /* * call-seq: * rxp.kcode => str * * Returns the character set code for the regexp. */ static VALUE rb_reg_kcode_m(re) VALUE re; { char *kcode; if (FL_TEST(re, KCODE_FIXED)) { switch (RBASIC(re)->flags & KCODE_MASK) { case KCODE_NONE: kcode = "none"; break; case KCODE_EUC: kcode = "euc"; break; case KCODE_SJIS: kcode = "sjis"; break; case KCODE_UTF8: kcode = "utf8"; break; default: rb_bug("unknown kcode - should not happen"); break; } return rb_str_new2(kcode); } return Qnil; } static Regexp* make_regexp(s, len, flags) const char *s; long len; int flags; { Regexp *rp; char *err; /* Handle escaped characters first. */ /* Build a copy of the string (in dest) with the escaped characters translated, and generate the regex from that. */ rp = ALLOC(Regexp); MEMZERO((char *)rp, Regexp, 1); rp->buffer = ALLOC_N(char, 16); rp->allocated = 16; rp->fastmap = ALLOC_N(char, 256); if (flags) { rp->options = flags; } err = re_compile_pattern(s, len, rp); if (err != NULL) { re_free_pattern(rp); rb_reg_raise(s, len, err, 0); return 0; } return rp; } /* * Document-class: MatchData * * MatchData is the type of the special variable $~, * and is the type of the object returned by Regexp#match and * Regexp#last_match. It encapsulates all the results of a pattern * match, results normally accessed through the special variables * $&, $', $`, $1, * $2, and so on. Matchdata is also known as * MatchingData. * */ VALUE rb_cMatch; static VALUE match_alloc _((VALUE)); static VALUE match_alloc(klass) VALUE klass; { NEWOBJ(match, struct RMatch); OBJSETUP(match, klass, T_MATCH); match->str = 0; match->regs = 0; match->regs = ALLOC(struct re_registers); MEMZERO(match->regs, struct re_registers, 1); return (VALUE)match; } static void match_check(VALUE match) { if (!RMATCH(match)->str) { rb_raise(rb_eTypeError, "uninitialized Match"); } } /* :nodoc: */ static VALUE match_init_copy(obj, orig) VALUE obj, orig; { if (obj == orig) return obj; if (!rb_obj_is_instance_of(orig, rb_obj_class(obj))) { rb_raise(rb_eTypeError, "wrong argument class"); } RMATCH(obj)->str = RMATCH(orig)->str; re_free_registers(RMATCH(obj)->regs); RMATCH(obj)->regs->allocated = 0; re_copy_registers(RMATCH(obj)->regs, RMATCH(orig)->regs); return obj; } /* * call-seq: * mtch.length => integer * mtch.size => integer * * Returns the number of elements in the match array. * * m = /(.)(.)(\d+)(\d)/.match("THX1138.") * m.length #=> 5 * m.size #=> 5 */ static VALUE match_size(match) VALUE match; { match_check(match); return INT2FIX(RMATCH(match)->regs->num_regs); } /* * call-seq: * mtch.offset(n) => array * * Returns a two-element array containing the beginning and ending offsets of * the nth match. * * m = /(.)(.)(\d+)(\d)/.match("THX1138.") * m.offset(0) #=> [1, 7] * m.offset(4) #=> [6, 7] */ static VALUE match_offset(match, n) VALUE match, n; { int i = NUM2INT(n); match_check(match); if (i < 0 || RMATCH(match)->regs->num_regs <= i) rb_raise(rb_eIndexError, "index %d out of matches", i); if (RMATCH(match)->regs->beg[i] < 0) return rb_assoc_new(Qnil, Qnil); return rb_assoc_new(INT2FIX(RMATCH(match)->regs->beg[i]), INT2FIX(RMATCH(match)->regs->end[i])); } /* * call-seq: * mtch.begin(n) => integer * * Returns the offset of the start of the nth element of the match * array in the string. * * m = /(.)(.)(\d+)(\d)/.match("THX1138.") * m.begin(0) #=> 1 * m.begin(2) #=> 2 */ static VALUE match_begin(match, n) VALUE match, n; { int i = NUM2INT(n); match_check(match); if (i < 0 || RMATCH(match)->regs->num_regs <= i) rb_raise(rb_eIndexError, "index %d out of matches", i); if (RMATCH(match)->regs->beg[i] < 0) return Qnil; return INT2FIX(RMATCH(match)->regs->beg[i]); } /* * call-seq: * mtch.end(n) => integer * * Returns the offset of the character immediately following the end of the * nth element of the match array in the string. * * m = /(.)(.)(\d+)(\d)/.match("THX1138.") * m.end(0) #=> 7 * m.end(2) #=> 3 */ static VALUE match_end(match, n) VALUE match, n; { int i = NUM2INT(n); match_check(match); if (i < 0 || RMATCH(match)->regs->num_regs <= i) rb_raise(rb_eIndexError, "index %d out of matches", i); if (RMATCH(match)->regs->beg[i] < 0) return Qnil; return INT2FIX(RMATCH(match)->regs->end[i]); } #define MATCH_BUSY FL_USER2 void rb_match_busy(match) VALUE match; { FL_SET(match, MATCH_BUSY); } int ruby_ignorecase; static int may_need_recompile; static void rb_reg_prepare_re(re) VALUE re; { int need_recompile = 0; int state; rb_reg_check(re); state = FL_TEST(re, REG_CASESTATE); /* ignorecase status */ if (ruby_ignorecase && !state) { FL_SET(re, REG_CASESTATE); RREGEXP(re)->ptr->options |= RE_OPTION_IGNORECASE; need_recompile = 1; } if (!ruby_ignorecase && state) { FL_UNSET(re, REG_CASESTATE); RREGEXP(re)->ptr->options &= ~RE_OPTION_IGNORECASE; need_recompile = 1; } if (!FL_TEST(re, KCODE_FIXED) && (RBASIC(re)->flags & KCODE_MASK) != reg_kcode) { need_recompile = 1; RBASIC(re)->flags &= ~KCODE_MASK; RBASIC(re)->flags |= reg_kcode; } if (need_recompile) { char *err; if (FL_TEST(re, KCODE_FIXED)) rb_kcode_set_option(re); rb_reg_check(re); RREGEXP(re)->ptr->fastmap_accurate = 0; err = re_compile_pattern(RREGEXP(re)->str, RREGEXP(re)->len, RREGEXP(re)->ptr); if (err != NULL) { rb_reg_raise(RREGEXP(re)->str, RREGEXP(re)->len, err, re); } } } long rb_reg_adjust_startpos(re, str, pos, reverse) VALUE re, str; long pos, reverse; { long range; rb_reg_check(re); if (may_need_recompile) rb_reg_prepare_re(re); if (FL_TEST(re, KCODE_FIXED)) rb_kcode_set_option(re); else if (reg_kcode != curr_kcode) rb_kcode_reset_option(); if (reverse) { range = -pos; } else { range = RSTRING(str)->len - pos; } return re_adjust_startpos(RREGEXP(re)->ptr, RSTRING(str)->ptr, RSTRING(str)->len, pos, range); } long rb_reg_search(re, str, pos, reverse) VALUE re, str; long pos, reverse; { long result; VALUE match; struct re_registers regs; long range; if (pos > RSTRING(str)->len || pos < 0) { rb_backref_set(Qnil); return -1; } rb_reg_check(re); if (may_need_recompile) rb_reg_prepare_re(re); if (FL_TEST(re, KCODE_FIXED)) rb_kcode_set_option(re); else if (reg_kcode != curr_kcode) rb_kcode_reset_option(); if (reverse) { range = -pos; } else { range = RSTRING(str)->len - pos; } MEMZERO(®s, struct re_registers, 1); result = re_search(RREGEXP(re)->ptr,RSTRING(str)->ptr,RSTRING(str)->len, pos, range, ®s); if (FL_TEST(re, KCODE_FIXED)) rb_kcode_reset_option(); if (result == -2) { rb_reg_raise(RREGEXP(re)->str, RREGEXP(re)->len, "Stack overflow in regexp matcher", re); } if (result < 0) { re_free_registers(®s); rb_backref_set(Qnil); return result; } match = rb_backref_get(); if (NIL_P(match) || FL_TEST(match, MATCH_BUSY)) { match = match_alloc(rb_cMatch); } else { if (rb_safe_level() >= 3) OBJ_TAINT(match); else FL_UNSET(match, FL_TAINT); } re_copy_registers(RMATCH(match)->regs, ®s); re_free_registers(®s); RMATCH(match)->str = rb_str_new4(str); rb_backref_set(match); OBJ_INFECT(match, re); OBJ_INFECT(match, str); return result; } VALUE rb_reg_nth_defined(nth, match) int nth; VALUE match; { if (NIL_P(match)) return Qnil; match_check(match); if (nth >= RMATCH(match)->regs->num_regs) { return Qnil; } if (nth < 0) { nth += RMATCH(match)->regs->num_regs; if (nth <= 0) return Qnil; } if (RMATCH(match)->BEG(nth) == -1) return Qfalse; return Qtrue; } VALUE rb_reg_nth_match(nth, match) int nth; VALUE match; { VALUE str; long start, end, len; if (NIL_P(match)) return Qnil; match_check(match); if (nth >= RMATCH(match)->regs->num_regs) { return Qnil; } if (nth < 0) { nth += RMATCH(match)->regs->num_regs; if (nth <= 0) return Qnil; } start = RMATCH(match)->BEG(nth); if (start == -1) return Qnil; end = RMATCH(match)->END(nth); len = end - start; str = rb_str_substr(RMATCH(match)->str, start, len); OBJ_INFECT(str, match); return str; } VALUE rb_reg_last_match(match) VALUE match; { return rb_reg_nth_match(0, match); } /* * call-seq: * mtch.pre_match => str * * Returns the portion of the original string before the current match. * Equivalent to the special variable $`. * * m = /(.)(.)(\d+)(\d)/.match("THX1138.") * m.pre_match #=> "T" */ VALUE rb_reg_match_pre(match) VALUE match; { VALUE str; if (NIL_P(match)) return Qnil; match_check(match); if (RMATCH(match)->BEG(0) == -1) return Qnil; str = rb_str_substr(RMATCH(match)->str, 0, RMATCH(match)->BEG(0)); if (OBJ_TAINTED(match)) OBJ_TAINT(str); return str; } /* * call-seq: * mtch.post_match => str * * Returns the portion of the original string after the current match. * Equivalent to the special variable $'. * * m = /(.)(.)(\d+)(\d)/.match("THX1138: The Movie") * m.post_match #=> ": The Movie" */ VALUE rb_reg_match_post(match) VALUE match; { VALUE str; long pos; if (NIL_P(match)) return Qnil; match_check(match); if (RMATCH(match)->BEG(0) == -1) return Qnil; str = RMATCH(match)->str; pos = RMATCH(match)->END(0); str = rb_str_substr(str, pos, RSTRING(str)->len - pos); if (OBJ_TAINTED(match)) OBJ_TAINT(str); return str; } VALUE rb_reg_match_last(match) VALUE match; { int i; if (NIL_P(match)) return Qnil; match_check(match); if (RMATCH(match)->BEG(0) == -1) return Qnil; for (i=RMATCH(match)->regs->num_regs-1; RMATCH(match)->BEG(i) == -1 && i > 0; i--) ; if (i == 0) return Qnil; return rb_reg_nth_match(i, match); } static VALUE last_match_getter() { return rb_reg_last_match(rb_backref_get()); } static VALUE prematch_getter() { return rb_reg_match_pre(rb_backref_get()); } static VALUE postmatch_getter() { return rb_reg_match_post(rb_backref_get()); } static VALUE last_paren_match_getter() { return rb_reg_match_last(rb_backref_get()); } static VALUE match_array(match, start) VALUE match; int start; { struct re_registers *regs; VALUE ary; VALUE target; int i; int taint = OBJ_TAINTED(match); match_check(match); regs = RMATCH(match)->regs; ary = rb_ary_new2(regs->num_regs); target = RMATCH(match)->str; for (i=start; inum_regs; i++) { if (regs->beg[i] == -1) { rb_ary_push(ary, Qnil); } else { VALUE str = rb_str_substr(target, regs->beg[i], regs->end[i]-regs->beg[i]); if (taint) OBJ_TAINT(str); rb_ary_push(ary, str); } } return ary; } /* [MG]:FIXME: I put parens around the /.../.match() in the first line of the second example to prevent the '*' followed by a '/' from ending the comment. */ /* * call-seq: * mtch.to_a => anArray * * Returns the array of matches. * * m = /(.)(.)(\d+)(\d)/.match("THX1138.") * m.to_a #=> ["HX1138", "H", "X", "113", "8"] * * Because to_a is called when expanding * *variable, there's a useful assignment * shortcut for extracting matched fields. This is slightly slower than * accessing the fields directly (as an intermediate array is * generated). * * all,f1,f2,f3 = *(/(.)(.)(\d+)(\d)/.match("THX1138.")) * all #=> "HX1138" * f1 #=> "H" * f2 #=> "X" * f3 #=> "113" */ static VALUE match_to_a(match) VALUE match; { return match_array(match, 0); } /* * call-seq: * mtch.captures => array * * Returns the array of captures; equivalent to mtch.to_a[1..-1]. * * f1,f2,f3,f4 = /(.)(.)(\d+)(\d)/.match("THX1138.").captures * f1 #=> "H" * f2 #=> "X" * f3 #=> "113" * f4 #=> "8" */ static VALUE match_captures(match) VALUE match; { return match_array(match, 1); } /* * call-seq: * mtch[i] => obj * mtch[start, length] => array * mtch[range] => array * * Match Reference---MatchData acts as an array, and may be * accessed using the normal array indexing techniques. mtch[0] is * equivalent to the special variable $&, and returns the entire * matched string. mtch[1], mtch[2], and so on return the values * of the matched backreferences (portions of the pattern between parentheses). * * m = /(.)(.)(\d+)(\d)/.match("THX1138.") * m[0] #=> "HX1138" * m[1, 2] #=> ["H", "X"] * m[1..3] #=> ["H", "X", "113"] * m[-3, 2] #=> ["X", "113"] */ static VALUE match_aref(argc, argv, match) int argc; VALUE *argv; VALUE match; { VALUE idx, rest; rb_scan_args(argc, argv, "11", &idx, &rest); if (!NIL_P(rest) || !FIXNUM_P(idx) || FIX2INT(idx) < 0) { return rb_ary_aref(argc, argv, match_to_a(match)); } return rb_reg_nth_match(FIX2INT(idx), match); } static VALUE match_entry _((VALUE, long)); static VALUE match_entry(match, n) VALUE match; long n; { return rb_reg_nth_match(n, match); } /* * call-seq: * mtch.select([index]*) => array * * Uses each index to access the matching values, returning an array of * the corresponding matches. * * m = /(.)(.)(\d+)(\d)/.match("THX1138: The Movie") * m.to_a #=> ["HX1138", "H", "X", "113", "8"] * m.select(0, 2, -2) #=> ["HX1138", "X", "113"] */ static VALUE match_values_at(argc, argv, match) int argc; VALUE *argv; VALUE match; { match_check(match); return rb_values_at(match, RMATCH(match)->regs->num_regs, argc, argv, match_entry); } /* * call-seq: * mtch.select([index]*) => array * * Uses each index to access the matching values, returning an * array of the corresponding matches. * * m = /(.)(.)(\d+)(\d)/.match("THX1138: The Movie") * m.to_a #=> ["HX1138", "H", "X", "113", "8"] * m.select(0, 2, -2) #=> ["HX1138", "X", "113"] */ static VALUE match_select(argc, argv, match) int argc; VALUE *argv; VALUE match; { if (argc > 0) { rb_raise(rb_eArgError, "wrong number of arguments (%d for 0)", argc); } else { struct re_registers *regs; VALUE target; VALUE result = rb_ary_new(); int i; int taint = OBJ_TAINTED(match); match_check(match); regs = RMATCH(match)->regs; target = RMATCH(match)->str; for (i=0; inum_regs; i++) { VALUE str = rb_str_substr(target, regs->beg[i], regs->end[i]-regs->beg[i]); if (taint) OBJ_TAINT(str); if (RTEST(rb_yield(str))) { rb_ary_push(result, str); } } return result; } } /* * call-seq: * mtch.to_s => str * * Returns the entire matched string. * * m = /(.)(.)(\d+)(\d)/.match("THX1138.") * m.to_s #=> "HX1138" */ static VALUE match_to_s(match) VALUE match; { VALUE str = rb_reg_last_match(match); if (NIL_P(str)) str = rb_str_new(0,0); if (OBJ_TAINTED(match)) OBJ_TAINT(str); if (OBJ_TAINTED(RMATCH(match)->str)) OBJ_TAINT(str); return str; } /* * call-seq: * mtch.string => str * * Returns a frozen copy of the string passed in to match. * * m = /(.)(.)(\d+)(\d)/.match("THX1138.") * m.string #=> "THX1138." */ static VALUE match_string(match) VALUE match; { match_check(match); return RMATCH(match)->str; /* str is frozen */ } VALUE rb_cRegexp; static void rb_reg_initialize(obj, s, len, options) VALUE obj; const char *s; long len; int options; /* CASEFOLD = 1 */ /* EXTENDED = 2 */ /* MULTILINE = 4 */ /* CODE_NONE = 16 */ /* CODE_EUC = 32 */ /* CODE_SJIS = 48 */ /* CODE_UTF8 = 64 */ { struct RRegexp *re = RREGEXP(obj); if (!OBJ_TAINTED(obj) && rb_safe_level() >= 4) rb_raise(rb_eSecurityError, "Insecure: can't modify regexp"); rb_check_frozen(obj); if (FL_TEST(obj, REG_LITERAL)) rb_raise(rb_eSecurityError, "can't modify literal regexp"); if (re->ptr) re_free_pattern(re->ptr); if (re->str) free(re->str); re->ptr = 0; re->str = 0; switch (options & ~0xf) { case 0: default: FL_SET(re, reg_kcode); break; case 16: kcode_none(re); break; case 32: kcode_euc(re); break; case 48: kcode_sjis(re); break; case 64: kcode_utf8(re); break; } if (options & ~0xf) { rb_kcode_set_option((VALUE)re); } if (ruby_ignorecase) { options |= RE_OPTION_IGNORECASE; FL_SET(re, REG_CASESTATE); } re->ptr = make_regexp(s, len, options & 0xf); re->str = ALLOC_N(char, len+1); memcpy(re->str, s, len); re->str[len] = '\0'; re->len = len; if (options & ~0xf) { rb_kcode_reset_option(); } if (ruby_in_compile) FL_SET(obj, REG_LITERAL); } static VALUE rb_reg_s_alloc _((VALUE)); static VALUE rb_reg_s_alloc(klass) VALUE klass; { NEWOBJ(re, struct RRegexp); OBJSETUP(re, klass, T_REGEXP); re->ptr = 0; re->len = 0; re->str = 0; return (VALUE)re; } VALUE rb_reg_new(s, len, options) const char *s; long len; int options; { VALUE re = rb_reg_s_alloc(rb_cRegexp); rb_reg_initialize(re, s, len, options); return (VALUE)re; } static int case_cache; static int kcode_cache; static VALUE reg_cache; VALUE rb_reg_regcomp(str) VALUE str; { volatile VALUE save_str = str; if (reg_cache && RREGEXP(reg_cache)->len == RSTRING(str)->len && case_cache == ruby_ignorecase && kcode_cache == reg_kcode && memcmp(RREGEXP(reg_cache)->str, RSTRING(str)->ptr, RSTRING(str)->len) == 0) return reg_cache; case_cache = ruby_ignorecase; kcode_cache = reg_kcode; return reg_cache = rb_reg_new(RSTRING(str)->ptr, RSTRING(str)->len, ruby_ignorecase); } static int rb_reg_cur_kcode(re) VALUE re; { if (FL_TEST(re, KCODE_FIXED)) { return RBASIC(re)->flags & KCODE_MASK; } return 0; } /* * call-seq: * rxp.hash => fixnum * * Produce a hash based on the text and options of this regular expression. */ static VALUE rb_reg_hash(re) VALUE re; { int hashval, len; char *p; rb_reg_check(re); hashval = RREGEXP(re)->ptr->options; len = RREGEXP(re)->len; p = RREGEXP(re)->str; while (len--) { hashval = hashval * 33 + *p++; } hashval = hashval + (hashval>>5); return INT2FIX(hashval); } /* * call-seq: * rxp == other_rxp => true or false * rxp.eql?(other_rxp) => true or false * * Equality---Two regexps are equal if their patterns are identical, they have * the same character set code, and their casefold? values are the * same. * * /abc/ == /abc/x #=> false * /abc/ == /abc/i #=> false * /abc/u == /abc/n #=> false */ static VALUE rb_reg_equal(re1, re2) VALUE re1, re2; { if (re1 == re2) return Qtrue; if (TYPE(re2) != T_REGEXP) return Qfalse; rb_reg_check(re1); rb_reg_check(re2); if (RREGEXP(re1)->len != RREGEXP(re2)->len) return Qfalse; if (memcmp(RREGEXP(re1)->str, RREGEXP(re2)->str, RREGEXP(re1)->len) == 0 && rb_reg_cur_kcode(re1) == rb_reg_cur_kcode(re2) && RREGEXP(re1)->ptr->options == RREGEXP(re2)->ptr->options) { return Qtrue; } return Qfalse; } /* * call-seq: * rxp.match(str) => matchdata or nil * * Returns a MatchData object describing the match, or * nil if there was no match. This is equivalent to retrieving the * value of the special variable $~ following a normal match. * * /(.)(.)(.)/.match("abc")[2] #=> "b" */ VALUE rb_reg_match(re, str) VALUE re, str; { long start; if (NIL_P(str)) { rb_backref_set(Qnil); return Qnil; } StringValue(str); start = rb_reg_search(re, str, 0, 0); if (start < 0) { return Qnil; } return LONG2FIX(start); } /* * call-seq: * rxp === str => true or false * * Case Equality---Synonym for Regexp#=~ used in case statements. * * a = "HELLO" * case a * when /^[a-z]*$/; print "Lower case\n" * when /^[A-Z]*$/; print "Upper case\n" * else; print "Mixed case\n" * end * * produces: * * Upper case */ VALUE rb_reg_eqq(re, str) VALUE re, str; { long start; if (TYPE(str) != T_STRING) { str = rb_check_string_type(str); if (NIL_P(str)) { rb_backref_set(Qnil); return Qfalse; } } StringValue(str); start = rb_reg_search(re, str, 0, 0); if (start < 0) { return Qfalse; } return Qtrue; } /* * call-seq: * ~ rxp => integer or nil * * Match---Matches rxp against the contents of $_. * Equivalent to rxp =~ $_. * * $_ = "input data" * ~ /at/ #=> 7 */ VALUE rb_reg_match2(re) VALUE re; { long start; VALUE line = rb_lastline_get(); if (TYPE(line) != T_STRING) { rb_backref_set(Qnil); return Qnil; } start = rb_reg_search(re, line, 0, 0); if (start < 0) { return Qnil; } return LONG2FIX(start); } /* * call-seq: * rxp.match(str) => matchdata or nil * * Returns a MatchData object describing the match, or * nil if there was no match. This is equivalent to retrieving the * value of the special variable $~ following a normal match. * * /(.)(.)(.)/.match("abc")[2] #=> "b" */ static VALUE rb_reg_match_m(re, str) VALUE re, str; { VALUE result = rb_reg_match(re, str); if (NIL_P(result)) return Qnil; result = rb_backref_get(); rb_match_busy(result); return result; } /* * Document-method: compile * * Synonym for Regexp.new */ /* * call-seq: * Regexp.new(string [, options [, lang]]) => regexp * Regexp.new(regexp) => regexp * Regexp.compile(string [, options [, lang]]) => regexp * Regexp.compile(regexp) => regexp * * Constructs a new regular expression from pattern, which can be either * a String or a Regexp (in which case that regexp's * options are propagated, and new options may not be specified (a change as of * Ruby 1.8). If options is a Fixnum, it should be one or * more of the constants Regexp::EXTENDED, * Regexp::IGNORECASE, and Regexp::MULTILINE, * or-ed together. Otherwise, if options is not * nil, the regexp will be case insensitive. The lang * parameter enables multibyte support for the regexp: `n', `N' = none, `e', * `E' = EUC, `s', `S' = SJIS, `u', `U' = UTF-8. * * r1 = Regexp.new('^a-z+:\\s+\w+') #=> /^a-z+:\s+\w+/ * r2 = Regexp.new('cat', true) #=> /cat/i * r3 = Regexp.new('dog', Regexp::EXTENDED) #=> /dog/x * r4 = Regexp.new(r2) #=> /cat/i */ static VALUE rb_reg_initialize_m(argc, argv, self) int argc; VALUE *argv; VALUE self; { const char *s; long len; int flags = 0; if (argc == 0 || argc > 3) { rb_raise(rb_eArgError, "wrong number of arguments"); } if (TYPE(argv[0]) == T_REGEXP) { if (argc > 1) { rb_warn("flags%s ignored", (argc == 3) ? " and encoding": ""); } rb_reg_check(argv[0]); flags = RREGEXP(argv[0])->ptr->options & 0xf; if (FL_TEST(argv[0], KCODE_FIXED)) { switch (RBASIC(argv[0])->flags & KCODE_MASK) { case KCODE_NONE: flags |= 16; break; case KCODE_EUC: flags |= 32; break; case KCODE_SJIS: flags |= 48; break; case KCODE_UTF8: flags |= 64; break; default: break; } } s = RREGEXP(argv[0])->str; len = RREGEXP(argv[0])->len; } else { if (argc >= 2) { if (FIXNUM_P(argv[1])) flags = FIX2INT(argv[1]); else if (RTEST(argv[1])) flags = RE_OPTION_IGNORECASE; } if (argc == 3 && !NIL_P(argv[2])) { char *kcode = StringValuePtr(argv[2]); flags &= ~0x70; switch (kcode[0]) { case 'n': case 'N': flags |= 16; break; case 'e': case 'E': flags |= 32; break; case 's': case 'S': flags |= 48; break; case 'u': case 'U': flags |= 64; break; default: break; } } s = StringValuePtr(argv[0]); len = RSTRING(argv[0])->len; } rb_reg_initialize(self, s, len, flags); return self; } VALUE rb_reg_quote(str) VALUE str; { char *s, *send, *t; VALUE tmp; int c; s = RSTRING(str)->ptr; send = s + RSTRING(str)->len; for (; s < send; s++) { c = *s; if (ismbchar(c)) { int n = mbclen(c); while (n-- && s < send) s++; s--; continue; } switch (c) { case '[': case ']': case '{': case '}': case '(': case ')': case '|': case '-': case '*': case '.': case '\\': case '?': case '+': case '^': case '$': case ' ': case '#': case '\t': case '\f': case '\n': case '\r': goto meta_found; } } return rb_str_new3(str); meta_found: tmp = rb_str_new(0, RSTRING(str)->len*2); t = RSTRING(tmp)->ptr; /* copy upto metacharacter */ memcpy(t, RSTRING(str)->ptr, s - RSTRING(str)->ptr); t += s - RSTRING(str)->ptr; for (; s < send; s++) { c = *s; if (ismbchar(c)) { int n = mbclen(c); while (n-- && s < send) *t++ = *s++; s--; continue; } switch (c) { case '[': case ']': case '{': case '}': case '(': case ')': case '|': case '-': case '*': case '.': case '\\': case '?': case '+': case '^': case '$': case '#': *t++ = '\\'; break; case ' ': *t++ = '\\'; *t++ = ' '; continue; case '\t': *t++ = '\\'; *t++ = 't'; continue; case '\n': *t++ = '\\'; *t++ = 'n'; continue; case '\r': *t++ = '\\'; *t++ = 'r'; continue; case '\f': *t++ = '\\'; *t++ = 'f'; continue; } *t++ = c; } rb_str_resize(tmp, t - RSTRING(tmp)->ptr); OBJ_INFECT(tmp, str); return tmp; } /* * call-seq: * Regexp.escape(str) => a_str * Regexp.quote(str) => a_str * * Escapes any characters that would have special meaning in a regular * expression. Returns a new escaped string, or self if no characters are * escaped. For any string, * Regexp.escape(str)=~str will be true. * * Regexp.escape('\\*?{}.') #=> \\\\\*\?\{\}\. */ static VALUE rb_reg_s_quote(argc, argv) int argc; VALUE *argv; { VALUE str, kcode; int kcode_saved = reg_kcode; rb_scan_args(argc, argv, "11", &str, &kcode); if (!NIL_P(kcode)) { rb_set_kcode(StringValuePtr(kcode)); curr_kcode = reg_kcode; reg_kcode = kcode_saved; } StringValue(str); str = rb_reg_quote(str); rb_kcode_reset_option(); return str; } int rb_kcode() { switch (reg_kcode) { case KCODE_EUC: return MBCTYPE_EUC; case KCODE_SJIS: return MBCTYPE_SJIS; case KCODE_UTF8: return MBCTYPE_UTF8; case KCODE_NONE: return MBCTYPE_ASCII; } rb_bug("wrong reg_kcode value (0x%x)", reg_kcode); } static int rb_reg_get_kcode(re) VALUE re; { switch (RBASIC(re)->flags & KCODE_MASK) { case KCODE_NONE: return 16; case KCODE_EUC: return 32; case KCODE_SJIS: return 48; case KCODE_UTF8: return 64; default: return 0; } } int rb_reg_options(re) VALUE re; { int options; rb_reg_check(re); options = RREGEXP(re)->ptr->options & (RE_OPTION_IGNORECASE|RE_OPTION_MULTILINE|RE_OPTION_EXTENDED); if (FL_TEST(re, KCODE_FIXED)) { options |= rb_reg_get_kcode(re); } return options; } /* * call-seq: * Regexp.union([pattern]*) => new_str * * Return a Regexp object that is the union of the given * patterns, i.e., will match any of its parts. The patterns * can be Regexp objects, in which case their options will be preserved, or * Strings. If no arguments are given, returns /(?!)/. * * Regexp.union #=> /(?!)/ * Regexp.union("penzance") #=> /penzance/ * Regexp.union("skiing", "sledding") #=> /skiing|sledding/ * Regexp.union(/dogs/, /cats/i) #=> /(?-mix:dogs)|(?i-mx:cats)/ */ static VALUE rb_reg_s_union(argc, argv) int argc; VALUE *argv; { if (argc == 0) { VALUE args[1]; args[0] = rb_str_new2("(?!)"); return rb_class_new_instance(1, args, rb_cRegexp); } else if (argc == 1) { VALUE v; v = rb_check_convert_type(argv[0], T_REGEXP, "Regexp", "to_regexp"); if (!NIL_P(v)) return v; else { VALUE args[1]; args[0] = rb_reg_s_quote(argc, argv); return rb_class_new_instance(1, args, rb_cRegexp); } } else { int i, kcode = -1; VALUE kcode_re = Qnil; VALUE source = rb_str_buf_new(0); VALUE args[3]; for (i = 0; i < argc; i++) { volatile VALUE v; if (0 < i) rb_str_buf_cat2(source, "|"); v = rb_check_convert_type(argv[i], T_REGEXP, "Regexp", "to_regexp"); if (!NIL_P(v)) { if (FL_TEST(v, KCODE_FIXED)) { if (kcode == -1) { kcode_re = v; kcode = RBASIC(v)->flags & KCODE_MASK; } else if ((RBASIC(v)->flags & KCODE_MASK) != kcode) { volatile VALUE str1, str2; str1 = rb_inspect(kcode_re); str2 = rb_inspect(v); rb_raise(rb_eArgError, "mixed kcode: %s and %s", RSTRING(str1)->ptr, RSTRING(str2)->ptr); } } v = rb_reg_to_s(v); } else { args[0] = argv[i]; v = rb_reg_s_quote(1, args); } rb_str_buf_append(source, v); } args[0] = source; args[1] = Qnil; switch (kcode) { case -1: args[2] = Qnil; break; case KCODE_NONE: args[2] = rb_str_new2("n"); break; case KCODE_EUC: args[2] = rb_str_new2("e"); break; case KCODE_SJIS: args[2] = rb_str_new2("s"); break; case KCODE_UTF8: args[2] = rb_str_new2("u"); break; } return rb_class_new_instance(3, args, rb_cRegexp); } } /* :nodoc: */ static VALUE rb_reg_init_copy(copy, re) VALUE copy, re; { if (copy == re) return copy; rb_check_frozen(copy); /* need better argument type check */ if (!rb_obj_is_instance_of(re, rb_obj_class(copy))) { rb_raise(rb_eTypeError, "wrong argument type"); } rb_reg_check(re); rb_reg_initialize(copy, RREGEXP(re)->str, RREGEXP(re)->len, rb_reg_options(re)); return copy; } VALUE rb_reg_regsub(str, src, regs) VALUE str, src; struct re_registers *regs; { VALUE val = 0; char *p, *s, *e, c; int no; p = s = RSTRING(str)->ptr; e = s + RSTRING(str)->len; while (s < e) { char *ss = s; c = *s++; if (ismbchar(c)) { s += mbclen(c) - 1; continue; } if (c != '\\' || s == e) continue; if (!val) { val = rb_str_buf_new(ss-p); rb_str_buf_cat(val, p, ss-p); } else { rb_str_buf_cat(val, p, ss-p); } c = *s++; p = s; switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': no = c - '0'; break; case '&': no = 0; break; case '`': rb_str_buf_cat(val, RSTRING(src)->ptr, BEG(0)); continue; case '\'': rb_str_buf_cat(val, RSTRING(src)->ptr+END(0), RSTRING(src)->len-END(0)); continue; case '+': no = regs->num_regs-1; while (BEG(no) == -1 && no > 0) no--; if (no == 0) continue; break; case '\\': rb_str_buf_cat(val, s-1, 1); continue; default: rb_str_buf_cat(val, s-2, 2); continue; } if (no >= 0) { if (no >= regs->num_regs) continue; if (BEG(no) == -1) continue; rb_str_buf_cat(val, RSTRING(src)->ptr+BEG(no), END(no)-BEG(no)); } } if (p < e) { if (!val) { val = rb_str_buf_new(e-p); rb_str_buf_cat(val, p, e-p); } else { rb_str_buf_cat(val, p, e-p); } } if (!val) return str; return val; } const char* rb_get_kcode() { switch (reg_kcode) { case KCODE_SJIS: return "SJIS"; case KCODE_EUC: return "EUC"; case KCODE_UTF8: return "UTF8"; default: return "NONE"; } } static VALUE kcode_getter() { return rb_str_new2(rb_get_kcode()); } void rb_set_kcode(code) const char *code; { if (code == 0) goto set_no_conversion; switch (code[0]) { case 'E': case 'e': reg_kcode = KCODE_EUC; re_mbcinit(MBCTYPE_EUC); break; case 'S': case 's': reg_kcode = KCODE_SJIS; re_mbcinit(MBCTYPE_SJIS); break; case 'U': case 'u': reg_kcode = KCODE_UTF8; re_mbcinit(MBCTYPE_UTF8); break; default: case 'N': case 'n': case 'A': case 'a': set_no_conversion: reg_kcode = KCODE_NONE; re_mbcinit(MBCTYPE_ASCII); break; } } static void kcode_setter(val) VALUE val; { may_need_recompile = 1; rb_set_kcode(StringValuePtr(val)); } static VALUE ignorecase_getter() { return ruby_ignorecase?Qtrue:Qfalse; } static void ignorecase_setter(val, id) VALUE val; ID id; { rb_warn("modifying %s is deprecated", rb_id2name(id)); may_need_recompile = 1; ruby_ignorecase = RTEST(val); } static VALUE match_getter() { VALUE match = rb_backref_get(); if (NIL_P(match)) return Qnil; rb_match_busy(match); return match; } static void match_setter(val) VALUE val; { if (!NIL_P(val)) { Check_Type(val, T_MATCH); } rb_backref_set(val); } /* * call-seq: * Regexp.last_match => matchdata * Regexp.last_match(fixnum) => str * * The first form returns the MatchData object generated by the * last successful pattern match. Equivalent to reading the global variable * $~. The second form returns the nth field in this * MatchData object. * * /c(.)t/ =~ 'cat' #=> 0 * Regexp.last_match #=> # * Regexp.last_match(0) #=> "cat" * Regexp.last_match(1) #=> "a" * Regexp.last_match(2) #=> nil */ static VALUE rb_reg_s_last_match(argc, argv) int argc; VALUE *argv; { VALUE nth; if (rb_scan_args(argc, argv, "01", &nth) == 1) { return rb_reg_nth_match(NUM2INT(nth), rb_backref_get()); } return match_getter(); } /* * Document-class: Regexp * * A Regexp holds a regular expression, used to match a pattern * against strings. Regexps are created using the /.../ and * %r{...} literals, and by the Regexp::new * constructor. * */ void Init_Regexp() { rb_eRegexpError = rb_define_class("RegexpError", rb_eStandardError); re_set_casetable(casetable); #if DEFAULT_KCODE == KCODE_EUC re_mbcinit(MBCTYPE_EUC); #else #if DEFAULT_KCODE == KCODE_SJIS re_mbcinit(MBCTYPE_SJIS); #else #if DEFAULT_KCODE == KCODE_UTF8 re_mbcinit(MBCTYPE_UTF8); #else re_mbcinit(MBCTYPE_ASCII); #endif #endif #endif rb_define_virtual_variable("$~", match_getter, match_setter); rb_define_virtual_variable("$&", last_match_getter, 0); rb_define_virtual_variable("$`", prematch_getter, 0); rb_define_virtual_variable("$'", postmatch_getter, 0); rb_define_virtual_variable("$+", last_paren_match_getter, 0); rb_define_virtual_variable("$=", ignorecase_getter, ignorecase_setter); rb_define_virtual_variable("$KCODE", kcode_getter, kcode_setter); rb_define_virtual_variable("$-K", kcode_getter, kcode_setter); rb_cRegexp = rb_define_class("Regexp", rb_cObject); rb_define_alloc_func(rb_cRegexp, rb_reg_s_alloc); rb_define_singleton_method(rb_cRegexp, "compile", rb_class_new_instance, -1); rb_define_singleton_method(rb_cRegexp, "quote", rb_reg_s_quote, -1); rb_define_singleton_method(rb_cRegexp, "escape", rb_reg_s_quote, -1); rb_define_singleton_method(rb_cRegexp, "union", rb_reg_s_union, -1); rb_define_singleton_method(rb_cRegexp, "last_match", rb_reg_s_last_match, -1); rb_define_method(rb_cRegexp, "initialize", rb_reg_initialize_m, -1); rb_define_method(rb_cRegexp, "initialize_copy", rb_reg_init_copy, 1); rb_define_method(rb_cRegexp, "hash", rb_reg_hash, 0); rb_define_method(rb_cRegexp, "eql?", rb_reg_equal, 1); rb_define_method(rb_cRegexp, "==", rb_reg_equal, 1); rb_define_method(rb_cRegexp, "=~", rb_reg_match, 1); rb_define_method(rb_cRegexp, "===", rb_reg_eqq, 1); rb_define_method(rb_cRegexp, "~", rb_reg_match2, 0); rb_define_method(rb_cRegexp, "match", rb_reg_match_m, 1); rb_define_method(rb_cRegexp, "to_s", rb_reg_to_s, 0); rb_define_method(rb_cRegexp, "inspect", rb_reg_inspect, 0); rb_define_method(rb_cRegexp, "source", rb_reg_source, 0); rb_define_method(rb_cRegexp, "casefold?", rb_reg_casefold_p, 0); rb_define_method(rb_cRegexp, "options", rb_reg_options_m, 0); rb_define_method(rb_cRegexp, "kcode", rb_reg_kcode_m, 0); rb_define_const(rb_cRegexp, "IGNORECASE", INT2FIX(RE_OPTION_IGNORECASE)); rb_define_const(rb_cRegexp, "EXTENDED", INT2FIX(RE_OPTION_EXTENDED)); rb_define_const(rb_cRegexp, "MULTILINE", INT2FIX(RE_OPTION_MULTILINE)); rb_global_variable(®_cache); rb_cMatch = rb_define_class("MatchData", rb_cObject); rb_define_global_const("MatchingData", rb_cMatch); rb_define_alloc_func(rb_cMatch, match_alloc); rb_undef_method(CLASS_OF(rb_cMatch), "new"); rb_define_method(rb_cMatch, "initialize_copy", match_init_copy, 1); rb_define_method(rb_cMatch, "size", match_size, 0); rb_define_method(rb_cMatch, "length", match_size, 0); rb_define_method(rb_cMatch, "offset", match_offset, 1); rb_define_method(rb_cMatch, "begin", match_begin, 1); rb_define_method(rb_cMatch, "end", match_end, 1); rb_define_method(rb_cMatch, "to_a", match_to_a, 0); rb_define_method(rb_cMatch, "[]", match_aref, -1); rb_define_method(rb_cMatch, "captures", match_captures, 0); rb_define_method(rb_cMatch, "select", match_select, -1); rb_define_method(rb_cMatch, "values_at", match_values_at, -1); rb_define_method(rb_cMatch, "pre_match", rb_reg_match_pre, 0); rb_define_method(rb_cMatch, "post_match", rb_reg_match_post, 0); rb_define_method(rb_cMatch, "to_s", match_to_s, 0); rb_define_method(rb_cMatch, "inspect", rb_any_to_s, 0); /* in object.c */ rb_define_method(rb_cMatch, "string", match_string, 0); } /* Extended regular expression matching and search library. Copyright (C) 1993, 94, 95, 96, 97, 98 Free Software Foundation, Inc. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with the GNU C Library; see the file LGPL. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Multi-byte extension added May, 1993 by t^2 (Takahiro Tanimoto) Last change: May 21, 1993 by t^2 */ /* removed gapped buffer support, multiple syntax support by matz */ /* Perl5 extension added by matz */ /* UTF-8 extension added Jan 16 1999 by Yoshida Masato */ #include "config.h" #ifdef HAVE_STRING_H # include #else # include #endif /* We write fatal error messages on standard error. */ #include /* isalpha(3) etc. are used for the character classes. */ #include #include #ifndef PARAMS # if defined __GNUC__ || (defined __STDC__ && __STDC__) # define PARAMS(args) args # else # define PARAMS(args) () # endif /* GCC. */ #endif /* Not PARAMS. */ #if defined(STDC_HEADERS) # include #else /* We need this for `regex.h', and perhaps for the Emacs include files. */ # include #endif #ifdef HAVE_STDLIB_H # include #endif #if !defined(__STDC__) && !defined(_MSC_VER) # define volatile #endif #ifdef HAVE_PROTOTYPES # define _(args) args #else # define _(args) () #endif #ifdef RUBY_PLATFORM #include "defines.h" #undef xmalloc #undef xrealloc #undef xcalloc #undef xfree # define RUBY extern int rb_prohibit_interrupt; extern int rb_trap_pending; void rb_trap_exec _((void)); # define CHECK_INTS do {\ if (!rb_prohibit_interrupt) {\ if (rb_trap_pending) rb_trap_exec();\ }\ } while (0) #endif /* Make alloca work the best possible way. */ #ifdef __GNUC__ # ifndef atarist # ifndef alloca # define alloca __builtin_alloca # endif # endif /* atarist */ #else # ifdef HAVE_ALLOCA_H # include # else # ifdef _AIX #pragma alloca # else # ifndef alloca /* predefined by HP cc +Olibcalls */ void *alloca (); # endif # endif /* AIX */ # endif /* HAVE_ALLOCA_H */ #endif /* __GNUC__ */ #ifdef HAVE_STRING_H # include #else # include #endif #define xmalloc malloc #define xrealloc realloc #define xcalloc calloc #define xfree free #ifdef C_ALLOCA #define FREE_VARIABLES() alloca(0) #else #define FREE_VARIABLES() #endif #define FREE_AND_RETURN_VOID(stackb) do { \ FREE_VARIABLES(); \ if (stackb != stacka) xfree(stackb); \ return; \ } while(0) #define FREE_AND_RETURN(stackb,val) do { \ FREE_VARIABLES(); \ if (stackb != stacka) xfree(stackb); \ return(val); \ } while(0) #define DOUBLE_STACK(type) do { \ type *stackx; \ unsigned int xlen = stacke - stackb; \ if (stackb == stacka) { \ stackx = (type*)xmalloc(2 * xlen * sizeof(type)); \ if (!stackx) goto memory_exhausted; \ memcpy(stackx, stackb, xlen * sizeof (type)); \ } \ else { \ stackx = (type*)xrealloc(stackb, 2 * xlen * sizeof(type)); \ if (!stackx) goto memory_exhausted; \ } \ /* Rearrange the pointers. */ \ stackp = stackx + (stackp - stackb); \ stackb = stackx; \ stacke = stackb + 2 * xlen; \ } while (0) #define RE_TALLOC(n,t) ((t*)alloca((n)*sizeof(t))) #define TMALLOC(n,t) ((t*)xmalloc((n)*sizeof(t))) #define TREALLOC(s,n,t) (s=((t*)xrealloc(s,(n)*sizeof(t)))) #define EXPAND_FAIL_STACK() DOUBLE_STACK(unsigned char*) #define ENSURE_FAIL_STACK(n) \ do { \ if (stacke - stackp <= (n)) { \ /* if (len > re_max_failures * MAX_NUM_FAILURE_ITEMS) \ { \ FREE_AND_RETURN(stackb,(-2)); \ }*/ \ \ /* Roughly double the size of the stack. */ \ EXPAND_FAIL_STACK(); \ } \ } while (0) /* Get the interface, including the syntax bits. */ #include "regex.h" /* Subroutines for re_compile_pattern. */ static void store_jump _((char*, int, char*)); static void insert_jump _((int, char*, char*, char*)); static void store_jump_n _((char*, int, char*, unsigned)); static void insert_jump_n _((int, char*, char*, char*, unsigned)); static void insert_op _((int, char*, char*)); static void insert_op_2 _((int, char*, char*, int, int)); static int memcmp_translate _((unsigned char*, unsigned char*, int)); /* Define the syntax stuff, so we can do the \<, \>, etc. */ /* This must be nonzero for the wordchar and notwordchar pattern commands in re_match. */ #define Sword 1 #define Sword2 2 #define SYNTAX(c) re_syntax_table[c] static char re_syntax_table[256]; static void init_syntax_once _((void)); static const unsigned char *translate = 0; static void init_regs _((struct re_registers*, unsigned int)); static void bm_init_skip _((int *, unsigned char*, int, const unsigned char*)); static int current_mbctype = MBCTYPE_ASCII; #undef P #ifdef RUBY #include "util.h" void rb_warn _((const char*, ...)); # define re_warning(x) rb_warn(x) #endif #ifndef re_warning # define re_warning(x) #endif static void init_syntax_once() { register int c; static int done = 0; if (done) return; memset(re_syntax_table, 0, sizeof re_syntax_table); for (c=0; c<=0x7f; c++) if (isalnum(c)) re_syntax_table[c] = Sword; re_syntax_table['_'] = Sword; for (c=0x80; c<=0xff; c++) if (isalnum(c)) re_syntax_table[c] = Sword2; done = 1; } void re_set_casetable(table) const char *table; { translate = (const unsigned char*)table; } /* Jim Meyering writes: "... Some ctype macros are valid only for character codes that isascii says are ASCII (SGI's IRIX-4.0.5 is one such system --when using /bin/cc or gcc but without giving an ansi option). So, all ctype uses should be through macros like ISPRINT... If STDC_HEADERS is defined, then autoconf has verified that the ctype macros don't need to be guarded with references to isascii. ... Defining isascii to 1 should let any compiler worth its salt eliminate the && through constant folding." Solaris defines some of these symbols so we must undefine them first. */ #undef ISASCII #if defined STDC_HEADERS || (!defined isascii && !defined HAVE_ISASCII) # define ISASCII(c) 1 #else # define ISASCII(c) isascii(c) #endif #ifdef isblank # define ISBLANK(c) (ISASCII(c) && isblank(c)) #else # define ISBLANK(c) ((c) == ' ' || (c) == '\t') #endif #ifdef isgraph # define ISGRAPH(c) (ISASCII(c) && isgraph(c)) #else # define ISGRAPH(c) (ISASCII(c) && isprint(c) && !isspace(c)) #endif #undef ISPRINT #define ISPRINT(c) (ISASCII(c) && isprint(c)) #define ISDIGIT(c) (ISASCII(c) && isdigit(c)) #define ISALNUM(c) (ISASCII(c) && isalnum(c)) #define ISALPHA(c) (ISASCII(c) && isalpha(c)) #define ISCNTRL(c) (ISASCII(c) && iscntrl(c)) #define ISLOWER(c) (ISASCII(c) && islower(c)) #define ISPUNCT(c) (ISASCII(c) && ispunct(c)) #define ISSPACE(c) (ISASCII(c) && isspace(c)) #define ISUPPER(c) (ISASCII(c) && isupper(c)) #define ISXDIGIT(c) (ISASCII(c) && isxdigit(c)) #ifndef NULL # define NULL (void *)0 #endif /* We remove any previous definition of `SIGN_EXTEND_CHAR', since ours (we hope) works properly with all combinations of machines, compilers, `char' and `unsigned char' argument types. (Per Bothner suggested the basic approach.) */ #undef SIGN_EXTEND_CHAR #if __STDC__ # define SIGN_EXTEND_CHAR(c) ((signed char)(c)) #else /* not __STDC__ */ /* As in Harbison and Steele. */ # define SIGN_EXTEND_CHAR(c) ((((unsigned char)(c)) ^ 128) - 128) #endif /* These are the command codes that appear in compiled regular expressions, one per byte. Some command codes are followed by argument bytes. A command code can specify any interpretation whatsoever for its arguments. Zero-bytes may appear in the compiled regular expression. The value of `exactn' is needed in search.c (search_buffer) in emacs. So regex.h defines a symbol `RE_EXACTN_VALUE' to be 1; the value of `exactn' we use here must also be 1. */ enum regexpcode { unused=0, exactn=1, /* Followed by one byte giving n, then by n literal bytes. */ begline, /* Fail unless at beginning of line. */ endline, /* Fail unless at end of line. */ begbuf, /* Succeeds if at beginning of buffer (if emacs) or at beginning of string to be matched (if not). */ endbuf, /* Analogously, for end of buffer/string. */ endbuf2, /* End of buffer/string, or newline just before it. */ begpos, /* Matches where last scan//gsub left off. */ jump, /* Followed by two bytes giving relative address to jump to. */ jump_past_alt,/* Same as jump, but marks the end of an alternative. */ on_failure_jump, /* Followed by two bytes giving relative address of place to resume at in case of failure. */ finalize_jump, /* Throw away latest failure point and then jump to address. */ maybe_finalize_jump, /* Like jump but finalize if safe to do so. This is used to jump back to the beginning of a repeat. If the command that follows this jump is clearly incompatible with the one at the beginning of the repeat, such that we can be sure that there is no use backtracking out of repetitions already completed, then we finalize. */ dummy_failure_jump, /* Jump, and push a dummy failure point. This failure point will be thrown away if an attempt is made to use it for a failure. A + construct makes this before the first repeat. Also use it as an intermediary kind of jump when compiling an or construct. */ push_dummy_failure, /* Push a dummy failure point and continue. Used at the end of alternatives. */ succeed_n, /* Used like on_failure_jump except has to succeed n times; then gets turned into an on_failure_jump. The relative address following it is useless until then. The address is followed by two bytes containing n. */ jump_n, /* Similar to jump, but jump n times only; also the relative address following is in turn followed by yet two more bytes containing n. */ try_next, /* Jump to next pattern for the first time, leaving this pattern on the failure stack. */ finalize_push, /* Finalize stack and push the beginning of the pattern on the stack to retry (used for non-greedy match) */ finalize_push_n, /* Similar to finalize_push, buf finalize n time only */ set_number_at, /* Set the following relative location to the subsequent number. */ anychar, /* Matches any (more or less) one character excluding newlines. */ anychar_repeat, /* Matches sequence of characters excluding newlines. */ charset, /* Matches any one char belonging to specified set. First following byte is number of bitmap bytes. Then come bytes for a bitmap saying which chars are in. Bits in each byte are ordered low-bit-first. A character is in the set if its bit is 1. A character too large to have a bit in the map is automatically not in the set. */ charset_not, /* Same parameters as charset, but match any character that is not one of those specified. */ start_memory, /* Start remembering the text that is matched, for storing in a memory register. Followed by one byte containing the register number. Register numbers must be in the range 0 through RE_NREGS. */ stop_memory, /* Stop remembering the text that is matched and store it in a memory register. Followed by one byte containing the register number. Register numbers must be in the range 0 through RE_NREGS. */ start_paren, /* Place holder at the start of (?:..). */ stop_paren, /* Place holder at the end of (?:..). */ casefold_on, /* Turn on casefold flag. */ casefold_off, /* Turn off casefold flag. */ option_set, /* Turn on multi line match (match with newlines). */ start_nowidth, /* Save string point to the stack. */ stop_nowidth, /* Restore string place at the point start_nowidth. */ pop_and_fail, /* Fail after popping nowidth entry from stack. */ stop_backtrack, /* Restore backtrack stack at the point start_nowidth. */ duplicate, /* Match a duplicate of something remembered. Followed by one byte containing the index of the memory register. */ wordchar, /* Matches any word-constituent character. */ notwordchar, /* Matches any char that is not a word-constituent. */ wordbeg, /* Succeeds if at word beginning. */ wordend, /* Succeeds if at word end. */ wordbound, /* Succeeds if at a word boundary. */ notwordbound /* Succeeds if not at a word boundary. */ }; /* Number of failure points to allocate space for initially, when matching. If this number is exceeded, more space is allocated, so it is not a hard limit. */ #ifndef NFAILURES #define NFAILURES 160 #endif /* Store NUMBER in two contiguous bytes starting at DESTINATION. */ #define STORE_NUMBER(destination, number) \ do { (destination)[0] = (number) & 0377; \ (destination)[1] = (number) >> 8; } while (0) /* Same as STORE_NUMBER, except increment the destination pointer to the byte after where the number is stored. Watch out that values for DESTINATION such as p + 1 won't work, whereas p will. */ #define STORE_NUMBER_AND_INCR(destination, number) \ do { STORE_NUMBER(destination, number); \ (destination) += 2; } while (0) /* Put into DESTINATION a number stored in two contingous bytes starting at SOURCE. */ #define EXTRACT_NUMBER(destination, source) \ do { (destination) = *(source) & 0377; \ (destination) += SIGN_EXTEND_CHAR(*(char*)((source) + 1)) << 8; } while (0) /* Same as EXTRACT_NUMBER, except increment the pointer for source to point to second byte of SOURCE. Note that SOURCE has to be a value such as p, not, e.g., p + 1. */ #define EXTRACT_NUMBER_AND_INCR(destination, source) \ do { EXTRACT_NUMBER(destination, source); \ (source) += 2; } while (0) /* Specify the precise syntax of regexps for compilation. This provides for compatibility for various utilities which historically have different, incompatible syntaxes. The argument SYNTAX is a bit-mask comprised of the various bits defined in regex.h. */ long re_set_syntax(syntax) long syntax; { /* obsolete */ return 0; } /* Macros for re_compile_pattern, which is found below these definitions. */ #define TRANSLATE_P() ((options&RE_OPTION_IGNORECASE) && translate) #define MAY_TRANSLATE() ((bufp->options&(RE_OPTION_IGNORECASE|RE_MAY_IGNORECASE)) && translate) /* Fetch the next character in the uncompiled pattern---translating it if necessary. Also cast from a signed character in the constant string passed to us by the user to an unsigned char that we can use as an array index (in, e.g., `translate'). */ #define PATFETCH(c) \ do {if (p == pend) goto end_of_pattern; \ c = (unsigned char) *p++; \ if (TRANSLATE_P()) c = (unsigned char)translate[c]; \ } while (0) /* Fetch the next character in the uncompiled pattern, with no translation. */ #define PATFETCH_RAW(c) \ do {if (p == pend) goto end_of_pattern; \ c = (unsigned char)*p++; \ } while (0) /* Go backwards one character in the pattern. */ #define PATUNFETCH p-- #define MBC2WC(c, p) \ do { \ if (current_mbctype == MBCTYPE_UTF8) { \ int n = mbclen(c) - 1; \ c &= (1<<(BYTEWIDTH-2-n)) - 1; \ while (n--) { \ c = c << 6 | (*p++ & ((1<<6)-1)); \ } \ } \ else { \ c <<= 8; \ c |= (unsigned char)*(p)++; \ } \ } while (0) #define PATFETCH_MBC(c) \ do { \ if (p + mbclen(c) - 1 >= pend) goto end_of_pattern; \ MBC2WC(c, p); \ } while(0) #define WC2MBC1ST(c) \ ((current_mbctype != MBCTYPE_UTF8) ? ((c<0x100) ? (c) : (((c)>>8)&0xff)) : utf8_firstbyte(c)) typedef unsigned int (*mbc_startpos_func_t) _((const char *string, unsigned int pos)); static unsigned int asc_startpos _((const char *string, unsigned int pos)); static unsigned int euc_startpos _((const char *string, unsigned int pos)); static unsigned int sjis_startpos _((const char *string, unsigned int pos)); static unsigned int utf8_startpos _((const char *string, unsigned int pos)); static const mbc_startpos_func_t mbc_startpos_func[4] = { asc_startpos, euc_startpos, sjis_startpos, utf8_startpos }; #define mbc_startpos(start, pos) (*mbc_startpos_func[current_mbctype])((start), (pos)) static unsigned int utf8_firstbyte(c) unsigned long c; { if (c < 0x80) return c; if (c <= 0x7ff) return ((c>>6)&0xff)|0xc0; if (c <= 0xffff) return ((c>>12)&0xff)|0xe0; if (c <= 0x1fffff) return ((c>>18)&0xff)|0xf0; if (c <= 0x3ffffff) return ((c>>24)&0xff)|0xf8; if (c <= 0x7fffffff) return ((c>>30)&0xff)|0xfc; #if SIZEOF_INT > 4 if (c <= 0xfffffffff) return 0xfe; #else return 0xfe; #endif } static void print_mbc(c) unsigned int c; { if (current_mbctype == MBCTYPE_UTF8) { if (c < 0x80) printf("%c", (int)c); else if (c <= 0x7ff) printf("%c%c", (int)utf8_firstbyte(c), (int)(c & 0x3f)); else if (c <= 0xffff) printf("%c%c%c", (int)utf8_firstbyte(c), (int)((c >> 6) & 0x3f), (int)(c & 0x3f)); else if (c <= 0x1fffff) printf("%c%c%c%c", (int)utf8_firstbyte(c), (int)((c >> 12) & 0x3f), (int)((c >> 6) & 0x3f), (int)(c & 0x3f)); else if (c <= 0x3ffffff) printf("%c%c%c%c%c", (int)utf8_firstbyte(c), (int)((c >> 18) & 0x3f), (int)((c >> 12) & 0x3f), (int)((c >> 6) & 0x3f), (int)(c & 0x3f)); else if (c <= 0x7fffffff) printf("%c%c%c%c%c%c", (int)utf8_firstbyte(c), (int)((c >> 24) & 0x3f), (int)((c >> 18) & 0x3f), (int)((c >> 12) & 0x3f), (int)((c >> 6) & 0x3f), (int)(c & 0x3f)); } else if (c < 0xff) { printf("\\%o", (int)c); } else { printf("%c%c", (int)(c >> BYTEWIDTH), (int)(c &0xff)); } } /* If the buffer isn't allocated when it comes in, use this. */ #define INIT_BUF_SIZE 28 /* Make sure we have at least N more bytes of space in buffer. */ #define GET_BUFFER_SPACE(n) \ do { \ while (b - bufp->buffer + (n) >= bufp->allocated) \ EXTEND_BUFFER; \ } while (0) /* Make sure we have one more byte of buffer space and then add CH to it. */ #define BUFPUSH(ch) \ do { \ GET_BUFFER_SPACE(1); \ *b++ = (char)(ch); \ } while (0) /* Extend the buffer by twice its current size via reallociation and reset the pointers that pointed into the old allocation to point to the correct places in the new allocation. If extending the buffer results in it being larger than 1 << 16, then flag memory exhausted. */ #define EXTEND_BUFFER \ do { char *old_buffer = bufp->buffer; \ if (bufp->allocated == (1L<<16)) goto too_big; \ bufp->allocated *= 2; \ if (bufp->allocated > (1L<<16)) bufp->allocated = (1L<<16); \ bufp->buffer = (char*)xrealloc(bufp->buffer, bufp->allocated); \ if (bufp->buffer == 0) \ goto memory_exhausted; \ b = (b - old_buffer) + bufp->buffer; \ if (fixup_alt_jump) \ fixup_alt_jump = (fixup_alt_jump - old_buffer) + bufp->buffer; \ if (laststart) \ laststart = (laststart - old_buffer) + bufp->buffer; \ begalt = (begalt - old_buffer) + bufp->buffer; \ if (pending_exact) \ pending_exact = (pending_exact - old_buffer) + bufp->buffer; \ } while (0) /* Set the bit for character C in a character set list. */ #define SET_LIST_BIT(c) \ (b[(unsigned char)(c) / BYTEWIDTH] \ |= 1 << ((unsigned char)(c) % BYTEWIDTH)) /* Get the next unsigned number in the uncompiled pattern. */ #define GET_UNSIGNED_NUMBER(num) \ do { if (p != pend) { \ PATFETCH(c); \ while (ISDIGIT(c)) { \ if (num < 0) \ num = 0; \ num = num * 10 + c - '0'; \ if (p == pend) \ break; \ PATFETCH(c); \ } \ } \ } while (0) #define STREQ(s1, s2) ((strcmp(s1, s2) == 0)) #define CHAR_CLASS_MAX_LENGTH 6 /* Namely, `xdigit'. */ #define IS_CHAR_CLASS(string) \ (STREQ(string, "alpha") || STREQ(string, "upper") \ || STREQ(string, "lower") || STREQ(string, "digit") \ || STREQ(string, "alnum") || STREQ(string, "xdigit") \ || STREQ(string, "space") || STREQ(string, "print") \ || STREQ(string, "punct") || STREQ(string, "graph") \ || STREQ(string, "cntrl") || STREQ(string, "blank")) #define STORE_MBC(p, c) \ do { \ (p)[0] = (unsigned char)(((c) >>24) & 0xff); \ (p)[1] = (unsigned char)(((c) >>16) & 0xff); \ (p)[2] = (unsigned char)(((c) >> 8) & 0xff); \ (p)[3] = (unsigned char)(((c) >> 0) & 0xff); \ } while (0) #define STORE_MBC_AND_INCR(p, c) \ do { \ *(p)++ = (unsigned char)(((c) >>24) & 0xff); \ *(p)++ = (unsigned char)(((c) >>16) & 0xff); \ *(p)++ = (unsigned char)(((c) >> 8) & 0xff); \ *(p)++ = (unsigned char)(((c) >> 0) & 0xff); \ } while (0) #define EXTRACT_MBC(p) \ ((unsigned int)((unsigned char)(p)[0] << 24 | \ (unsigned char)(p)[1] << 16 | \ (unsigned char)(p)[2] << 8 | \ (unsigned char)(p)[3])) #define EXTRACT_MBC_AND_INCR(p) \ ((unsigned int)((p) += 4, \ (unsigned char)(p)[-4] << 24 | \ (unsigned char)(p)[-3] << 16 | \ (unsigned char)(p)[-2] << 8 | \ (unsigned char)(p)[-1])) #define EXTRACT_UNSIGNED(p) \ ((unsigned char)(p)[0] | (unsigned char)(p)[1] << 8) #define EXTRACT_UNSIGNED_AND_INCR(p) \ ((p) += 2, (unsigned char)(p)[-2] | (unsigned char)(p)[-1] << 8) /* Handle (mb)?charset(_not)?. Structure of mbcharset(_not)? in compiled pattern. struct { unsinged char id; mbcharset(_not)? unsigned char sbc_size; unsigned char sbc_map[sbc_size]; same as charset(_not)? up to here. unsigned short mbc_size; number of intervals. struct { unsigned long beg; beginning of interval. unsigned long end; end of interval. } intervals[mbc_size]; }; */ static void set_list_bits(c1, c2, b) unsigned long c1, c2; unsigned char *b; { unsigned char sbc_size = b[-1]; unsigned short mbc_size = EXTRACT_UNSIGNED(&b[sbc_size]); unsigned short beg, end, upb; if (c1 > c2) return; b = &b[sbc_size + 2]; for (beg = 0, upb = mbc_size; beg < upb; ) { unsigned short mid = (unsigned short)(beg + upb) >> 1; if ((int)c1 - 1 > (int)EXTRACT_MBC(&b[mid*8+4])) beg = mid + 1; else upb = mid; } for (end = beg, upb = mbc_size; end < upb; ) { unsigned short mid = (unsigned short)(end + upb) >> 1; if ((int)c2 >= (int)EXTRACT_MBC(&b[mid*8]) - 1) end = mid + 1; else upb = mid; } if (beg != end) { if (c1 > EXTRACT_MBC(&b[beg*8])) c1 = EXTRACT_MBC(&b[beg*8]); if (c2 < EXTRACT_MBC(&b[(end - 1)*8+4])) c2 = EXTRACT_MBC(&b[(end - 1)*8+4]); } if (end < mbc_size && end != beg + 1) /* NOTE: memcpy() would not work here. */ memmove(&b[(beg + 1)*8], &b[end*8], (mbc_size - end)*8); STORE_MBC(&b[beg*8 + 0], c1); STORE_MBC(&b[beg*8 + 4], c2); mbc_size += beg - end + 1; STORE_NUMBER(&b[-2], mbc_size); } static int is_in_list_sbc(c, b) unsigned long c; const unsigned char *b; { unsigned short size; size = *b++; return ((int)c / BYTEWIDTH < (int)size && b[c / BYTEWIDTH] & 1 << c % BYTEWIDTH); } static int is_in_list_mbc(c, b) unsigned long c; const unsigned char *b; { unsigned short size; unsigned short i, j; size = *b++; b += size + 2; size = EXTRACT_UNSIGNED(&b[-2]); if (size == 0) return 0; for (i = 0, j = size; i < j; ) { unsigned short k = (unsigned short)(i + j) >> 1; if (c > EXTRACT_MBC(&b[k*8+4])) i = k + 1; else j = k; } if (i < size && EXTRACT_MBC(&b[i*8]) <= c) return 1; return 0; } static int is_in_list(c, b) unsigned long c; const unsigned char *b; { return is_in_list_sbc(c, b) || (current_mbctype ? is_in_list_mbc(c, b) : 0); } static void print_partial_compiled_pattern(start, end) unsigned char *start; unsigned char *end; { int mcnt, mcnt2; unsigned char *p = start; unsigned char *pend = end; if (start == NULL) { printf("(null)\n"); return; } /* Loop over pattern commands. */ while (p < pend) { switch ((enum regexpcode)*p++) { case unused: printf("/unused"); break; case exactn: mcnt = *p++; printf("/exactn/%d", mcnt); do { putchar('/'); printf("%c", *p++); } while (--mcnt); break; case start_memory: mcnt = *p++; printf("/start_memory/%d/%d", mcnt, *p++); break; case stop_memory: mcnt = *p++; printf("/stop_memory/%d/%d", mcnt, *p++); break; case start_paren: printf("/start_paren"); break; case stop_paren: printf("/stop_paren"); break; case casefold_on: printf("/casefold_on"); break; case casefold_off: printf("/casefold_off"); break; case option_set: printf("/option_set/%d", *p++); break; case start_nowidth: EXTRACT_NUMBER_AND_INCR(mcnt, p); printf("/start_nowidth//%d", mcnt); break; case stop_nowidth: printf("/stop_nowidth//"); p += 2; break; case pop_and_fail: printf("/pop_and_fail"); break; case stop_backtrack: printf("/stop_backtrack//"); p += 2; break; case duplicate: printf("/duplicate/%d", *p++); break; case anychar: printf("/anychar"); break; case anychar_repeat: printf("/anychar_repeat"); break; case charset: case charset_not: { register int c; printf("/charset%s", (enum regexpcode)*(p - 1) == charset_not ? "_not" : ""); mcnt = *p++; printf("/%d", mcnt); for (c = 0; c < mcnt; c++) { unsigned bit; unsigned char map_byte = p[c]; putchar('/'); for (bit = 0; bit < BYTEWIDTH; bit++) if (map_byte & (1 << bit)) printf("%c", c * BYTEWIDTH + bit); } p += mcnt; mcnt = EXTRACT_UNSIGNED_AND_INCR(p); putchar('/'); while (mcnt--) { print_mbc(EXTRACT_MBC_AND_INCR(p)); putchar('-'); print_mbc(EXTRACT_MBC_AND_INCR(p)); } break; } case begline: printf("/begline"); break; case endline: printf("/endline"); break; case on_failure_jump: EXTRACT_NUMBER_AND_INCR(mcnt, p); printf("/on_failure_jump//%d", mcnt); break; case dummy_failure_jump: EXTRACT_NUMBER_AND_INCR(mcnt, p); printf("/dummy_failure_jump//%d", mcnt); break; case push_dummy_failure: printf("/push_dummy_failure"); break; case finalize_jump: EXTRACT_NUMBER_AND_INCR(mcnt, p); printf("/finalize_jump//%d", mcnt); break; case maybe_finalize_jump: EXTRACT_NUMBER_AND_INCR(mcnt, p); printf("/maybe_finalize_jump//%d", mcnt); break; case jump_past_alt: EXTRACT_NUMBER_AND_INCR(mcnt, p); printf("/jump_past_alt//%d", mcnt); break; case jump: EXTRACT_NUMBER_AND_INCR(mcnt, p); printf("/jump//%d", mcnt); break; case succeed_n: EXTRACT_NUMBER_AND_INCR(mcnt, p); EXTRACT_NUMBER_AND_INCR(mcnt2, p); printf("/succeed_n//%d//%d", mcnt, mcnt2); break; case jump_n: EXTRACT_NUMBER_AND_INCR(mcnt, p); EXTRACT_NUMBER_AND_INCR(mcnt2, p); printf("/jump_n//%d//%d", mcnt, mcnt2); break; case set_number_at: EXTRACT_NUMBER_AND_INCR(mcnt, p); EXTRACT_NUMBER_AND_INCR(mcnt2, p); printf("/set_number_at//%d//%d", mcnt, mcnt2); break; case try_next: EXTRACT_NUMBER_AND_INCR(mcnt, p); printf("/try_next//%d", mcnt); break; case finalize_push: EXTRACT_NUMBER_AND_INCR(mcnt, p); printf("/finalize_push//%d", mcnt); break; case finalize_push_n: EXTRACT_NUMBER_AND_INCR(mcnt, p); EXTRACT_NUMBER_AND_INCR(mcnt2, p); printf("/finalize_push_n//%d//%d", mcnt, mcnt2); break; case wordbound: printf("/wordbound"); break; case notwordbound: printf("/notwordbound"); break; case wordbeg: printf("/wordbeg"); break; case wordend: printf("/wordend"); case wordchar: printf("/wordchar"); break; case notwordchar: printf("/notwordchar"); break; case begbuf: printf("/begbuf"); break; case endbuf: printf("/endbuf"); break; case endbuf2: printf("/endbuf2"); break; case begpos: printf("/begpos"); break; default: printf("?%d", *(p-1)); } } printf("/\n"); } static void print_compiled_pattern(bufp) struct re_pattern_buffer *bufp; { unsigned char *buffer = (unsigned char*)bufp->buffer; print_partial_compiled_pattern(buffer, buffer + bufp->used); } static char* calculate_must_string(start, end) char *start; char *end; { int mcnt; int max = 0; unsigned char *p = (unsigned char *)start; unsigned char *pend = (unsigned char *)end; char *must = 0; if (start == NULL) return 0; /* Loop over pattern commands. */ while (p < pend) { switch ((enum regexpcode)*p++) { case unused: break; case exactn: mcnt = *p; if (mcnt > max) { must = (char *)p; max = mcnt; } p += mcnt+1; break; case start_memory: case stop_memory: p += 2; break; case duplicate: case option_set: p++; break; case casefold_on: case casefold_off: return 0; /* should not check must_string */ case pop_and_fail: case anychar: case anychar_repeat: case begline: case endline: case wordbound: case notwordbound: case wordbeg: case wordend: case wordchar: case notwordchar: case begbuf: case endbuf: case endbuf2: case begpos: case push_dummy_failure: case start_paren: case stop_paren: break; case charset: case charset_not: mcnt = *p++; p += mcnt; mcnt = EXTRACT_UNSIGNED_AND_INCR(p); while (mcnt--) { p += 8; } break; case on_failure_jump: EXTRACT_NUMBER_AND_INCR(mcnt, p); if (mcnt > 0) p += mcnt; if ((enum regexpcode)p[-3] == jump) { p -= 2; EXTRACT_NUMBER_AND_INCR(mcnt, p); if (mcnt > 0) p += mcnt; } break; case dummy_failure_jump: case succeed_n: case try_next: case jump: EXTRACT_NUMBER_AND_INCR(mcnt, p); if (mcnt > 0) p += mcnt; break; case start_nowidth: case stop_nowidth: case stop_backtrack: case finalize_jump: case maybe_finalize_jump: case finalize_push: p += 2; break; case jump_n: case set_number_at: case finalize_push_n: p += 4; break; default: break; } } return must; } static unsigned int read_backslash(c) int c; { switch (c) { case 'n': return '\n'; case 't': return '\t'; case 'r': return '\r'; case 'f': return '\f'; case 'v': return '\v'; case 'a': return '\007'; case 'b': return '\010'; case 'e': return '\033'; } return c; } static unsigned int read_special(p, pend, pp) const char *p, *pend, **pp; { int c; PATFETCH_RAW(c); switch (c) { case 'M': PATFETCH_RAW(c); if (c != '-') return -1; PATFETCH_RAW(c); *pp = p; if (c == '\\') { return read_special(--p, pend, pp) | 0x80; } else if (c == -1) return ~0; else { return ((c & 0xff) | 0x80); } case 'C': PATFETCH_RAW(c); if (c != '-') return ~0; case 'c': PATFETCH_RAW(c); *pp = p; if (c == '\\') { c = read_special(--p, pend, pp); } else if (c == '?') return 0177; else if (c == -1) return ~0; return c & 0x9f; default: *pp = p + 1; return read_backslash(c); } end_of_pattern: return ~0; } /* re_compile_pattern takes a regular-expression string and converts it into a buffer full of byte commands for matching. PATTERN is the address of the pattern string SIZE is the length of it. BUFP is a struct re_pattern_buffer * which points to the info on where to store the byte commands. This structure contains a char * which points to the actual space, which should have been obtained with malloc. re_compile_pattern may use realloc to grow the buffer space. The number of bytes of commands can be found out by looking in the `struct re_pattern_buffer' that bufp pointed to, after re_compile_pattern returns. */ char * re_compile_pattern(pattern, size, bufp) const char *pattern; int size; struct re_pattern_buffer *bufp; { register char *b = bufp->buffer; register const char *p = pattern; const char *nextp; const char *pend = pattern + size; register unsigned int c, c1 = 0; const char *p0; int numlen; #define ERROR_MSG_MAX_SIZE 200 static char error_msg[ERROR_MSG_MAX_SIZE+1]; /* Address of the count-byte of the most recently inserted `exactn' command. This makes it possible to tell whether a new exact-match character can be added to that command or requires a new `exactn' command. */ char *pending_exact = 0; /* Address of the place where a forward-jump should go to the end of the containing expression. Each alternative of an `or', except the last, ends with a forward-jump of this sort. */ char *fixup_alt_jump = 0; /* Address of start of the most recently finished expression. This tells postfix * where to find the start of its operand. */ char *laststart = 0; /* In processing a repeat, 1 means zero matches is allowed. */ char zero_times_ok; /* In processing a repeat, 1 means many matches is allowed. */ char many_times_ok; /* In processing a repeat, 1 means non-greedy matches. */ char greedy; /* Address of beginning of regexp, or inside of last (. */ char *begalt = b; /* Place in the uncompiled pattern (i.e., the {) to which to go back if the interval is invalid. */ const char *beg_interval; /* In processing an interval, at least this many matches must be made. */ int lower_bound; /* In processing an interval, at most this many matches can be made. */ int upper_bound; /* Stack of information saved by ( and restored by ). Five stack elements are pushed by each (: First, the value of b. Second, the value of fixup_alt_jump. Third, the value of begalt. Fourth, the value of regnum. Fifth, the type of the paren. */ int stacka[40]; int *stackb = stacka; int *stackp = stackb; int *stacke = stackb + 40; /* Counts ('s as they are encountered. Remembered for the matching ), where it becomes the register number to put in the stop_memory command. */ int regnum = 1; int range = 0; int had_mbchar = 0; int had_num_literal = 0; int had_char_class = 0; int options = bufp->options; bufp->fastmap_accurate = 0; bufp->must = 0; bufp->must_skip = 0; /* Initialize the syntax table. */ init_syntax_once(); if (bufp->allocated == 0) { bufp->allocated = INIT_BUF_SIZE; /* EXTEND_BUFFER loses when bufp->allocated is 0. */ bufp->buffer = (char*)xrealloc(bufp->buffer, INIT_BUF_SIZE); if (!bufp->buffer) goto memory_exhausted; /* this not happen */ begalt = b = bufp->buffer; } while (p != pend) { PATFETCH(c); switch (c) { case '$': if (bufp->options & RE_OPTION_SINGLELINE) { BUFPUSH(endbuf); } else { p0 = p; /* When testing what follows the $, look past the \-constructs that don't consume anything. */ while (p0 != pend) { if (*p0 == '\\' && p0 + 1 != pend && (p0[1] == 'b' || p0[1] == 'B')) p0 += 2; else break; } BUFPUSH(endline); } break; case '^': if (bufp->options & RE_OPTION_SINGLELINE) BUFPUSH(begbuf); else BUFPUSH(begline); break; case '+': case '?': case '*': /* If there is no previous pattern, char not special. */ if (!laststart) { snprintf(error_msg, ERROR_MSG_MAX_SIZE, "invalid regular expression; there's no previous pattern, to which '%c' would define cardinality at %d", c, p-pattern); FREE_AND_RETURN(stackb, error_msg); } /* If there is a sequence of repetition chars, collapse it down to just one. */ zero_times_ok = c != '+'; many_times_ok = c != '?'; greedy = 1; if (p != pend) { PATFETCH(c); switch (c) { case '?': greedy = 0; break; case '*': case '+': goto nested_meta; default: PATUNFETCH; break; } } repeat: /* Star, etc. applied to an empty pattern is equivalent to an empty pattern. */ if (!laststart) break; if (greedy && many_times_ok && *laststart == anychar && b - laststart <= 2) { if (b[-1] == stop_paren) b--; if (zero_times_ok) *laststart = anychar_repeat; else { BUFPUSH(anychar_repeat); } break; } /* Now we know whether or not zero matches is allowed and also whether or not two or more matches is allowed. */ if (many_times_ok) { /* If more than one repetition is allowed, put in at the end a backward relative jump from b to before the next jump we're going to put in below (which jumps from laststart to after this jump). */ GET_BUFFER_SPACE(3); store_jump(b,greedy?maybe_finalize_jump:finalize_push,laststart-3); b += 3; /* Because store_jump put stuff here. */ } /* On failure, jump from laststart to next pattern, which will be the end of the buffer after this jump is inserted. */ GET_BUFFER_SPACE(3); insert_jump(on_failure_jump, laststart, b + 3, b); b += 3; if (zero_times_ok) { if (greedy == 0) { GET_BUFFER_SPACE(3); insert_jump(try_next, laststart, b + 3, b); b += 3; } } else { /* At least one repetition is required, so insert a `dummy_failure_jump' before the initial `on_failure_jump' instruction of the loop. This effects a skip over that instruction the first time we hit that loop. */ GET_BUFFER_SPACE(3); insert_jump(dummy_failure_jump, laststart, laststart + 6, b); b += 3; } break; case '.': laststart = b; BUFPUSH(anychar); break; case '[': if (p == pend) FREE_AND_RETURN(stackb, "invalid regular expression; '[' can't be the last character ie. can't start range at the end of pattern"); while ((b - bufp->buffer + 9 + (1 << BYTEWIDTH) / BYTEWIDTH) > bufp->allocated) EXTEND_BUFFER; laststart = b; if (*p == '^') { BUFPUSH(charset_not); p++; } else BUFPUSH(charset); p0 = p; BUFPUSH((1 << BYTEWIDTH) / BYTEWIDTH); /* Clear the whole map */ memset(b, 0, (1 << BYTEWIDTH) / BYTEWIDTH + 2); had_mbchar = 0; had_num_literal = 0; had_char_class = 0; /* Read in characters and ranges, setting map bits. */ for (;;) { int size; unsigned last = (unsigned)-1; if ((size = EXTRACT_UNSIGNED(&b[(1 << BYTEWIDTH) / BYTEWIDTH])) || current_mbctype) { /* Ensure the space is enough to hold another interval of multi-byte chars in charset(_not)?. */ size = (1 << BYTEWIDTH) / BYTEWIDTH + 2 + size*8 + 8; while (b + size + 1 > bufp->buffer + bufp->allocated) EXTEND_BUFFER; } range_retry: if (range && had_char_class) { FREE_AND_RETURN(stackb, "invalid regular expression; can't use character class as an end value of range"); } PATFETCH_RAW(c); if (c == ']') { if (p == p0 + 1) { if (p == pend) FREE_AND_RETURN(stackb, "invalid regular expression; empty character class"); re_warning("character class has `]' without escape"); } else /* Stop if this isn't merely a ] inside a bracket expression, but rather the end of a bracket expression. */ break; } /* Look ahead to see if it's a range when the last thing was a character class. */ if (had_char_class && c == '-' && *p != ']') FREE_AND_RETURN(stackb, "invalid regular expression; can't use character class as a start value of range"); if (ismbchar(c)) { PATFETCH_MBC(c); had_mbchar++; } had_char_class = 0; if (c == '-' && ((p != p0 + 1 && *p != ']') || (p[0] == '-' && p[1] != ']') || range)) re_warning("character class has `-' without escape"); if (c == '[' && *p != ':') re_warning("character class has `[' without escape"); /* \ escapes characters when inside [...]. */ if (c == '\\') { PATFETCH_RAW(c); switch (c) { case 'w': for (c = 0; c < (1 << BYTEWIDTH); c++) { if (SYNTAX(c) == Sword || (!current_mbctype && SYNTAX(c) == Sword2)) SET_LIST_BIT(c); } if (current_mbctype) { set_list_bits(0x80, 0xffffffff, b); } had_char_class = 1; last = -1; continue; case 'W': for (c = 0; c < (1 << BYTEWIDTH); c++) { if (SYNTAX(c) != Sword && ((current_mbctype && !re_mbctab[c]) || (!current_mbctype && SYNTAX(c) != Sword2))) SET_LIST_BIT(c); } had_char_class = 1; last = -1; continue; case 's': for (c = 0; c < 256; c++) if (ISSPACE(c)) SET_LIST_BIT(c); had_char_class = 1; last = -1; continue; case 'S': for (c = 0; c < 256; c++) if (!ISSPACE(c)) SET_LIST_BIT(c); if (current_mbctype) set_list_bits(0x80, 0xffffffff, b); had_char_class = 1; last = -1; continue; case 'd': for (c = '0'; c <= '9'; c++) SET_LIST_BIT(c); had_char_class = 1; last = -1; continue; case 'D': for (c = 0; c < 256; c++) if (!ISDIGIT(c)) SET_LIST_BIT(c); if (current_mbctype) set_list_bits(0x80, 0xffffffff, b); had_char_class = 1; last = -1; continue; case 'x': c = scan_hex(p, 2, &numlen); if (numlen == 0) goto invalid_escape; p += numlen; had_num_literal = 1; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': PATUNFETCH; c = scan_oct(p, 3, &numlen); p += numlen; had_num_literal = 1; break; case 'M': case 'C': case 'c': { const char *pp; --p; c = read_special(p, pend, &pp); if (c > 255) goto invalid_escape; p = pp; had_num_literal = 1; } break; default: c = read_backslash(c); if (ismbchar(c)) { PATFETCH_MBC(c); had_mbchar++; } break; } } else if (c == '[' && *p == ':') { /* [:...:] */ /* Leave room for the null. */ char str[CHAR_CLASS_MAX_LENGTH + 1]; PATFETCH_RAW(c); c1 = 0; /* If pattern is `[[:'. */ if (p == pend) FREE_AND_RETURN(stackb, "invalid regular expression; re can't end '[[:'"); for (;;) { PATFETCH_RAW(c); if (c == ':' || c == ']' || p == pend || c1 == CHAR_CLASS_MAX_LENGTH) break; str[c1++] = c; } str[c1] = '\0'; /* If isn't a word bracketed by `[:' and `:]': undo the ending character, the letters, and the leading `:' and `['. */ if (c == ':' && *p == ']') { int ch; char is_alnum = STREQ(str, "alnum"); char is_alpha = STREQ(str, "alpha"); char is_blank = STREQ(str, "blank"); char is_cntrl = STREQ(str, "cntrl"); char is_digit = STREQ(str, "digit"); char is_graph = STREQ(str, "graph"); char is_lower = STREQ(str, "lower"); char is_print = STREQ(str, "print"); char is_punct = STREQ(str, "punct"); char is_space = STREQ(str, "space"); char is_upper = STREQ(str, "upper"); char is_xdigit = STREQ(str, "xdigit"); if (!IS_CHAR_CLASS(str)){ snprintf(error_msg, ERROR_MSG_MAX_SIZE, "invalid regular expression; [:%s:] is not a character class", str); FREE_AND_RETURN(stackb, error_msg); } /* Throw away the ] at the end of the character class. */ PATFETCH(c); if (p == pend) FREE_AND_RETURN(stackb, "invalid regular expression; range doesn't have ending ']' after a character class"); for (ch = 0; ch < 1 << BYTEWIDTH; ch++) { if ( (is_alnum && ISALNUM(ch)) || (is_alpha && ISALPHA(ch)) || (is_blank && ISBLANK(ch)) || (is_cntrl && ISCNTRL(ch)) || (is_digit && ISDIGIT(ch)) || (is_graph && ISGRAPH(ch)) || (is_lower && ISLOWER(ch)) || (is_print && ISPRINT(ch)) || (is_punct && ISPUNCT(ch)) || (is_space && ISSPACE(ch)) || (is_upper && ISUPPER(ch)) || (is_xdigit && ISXDIGIT(ch))) SET_LIST_BIT(ch); } had_char_class = 1; continue; } else { c1 += 2; while (c1--) PATUNFETCH; re_warning("character class has `[' without escape"); c = '['; } } /* Get a range. */ if (range) { if (last > c) goto invalid_pattern; range = 0; if (had_mbchar == 0) { if (TRANSLATE_P()) { for (;last<=c;last++) SET_LIST_BIT(translate[last]); } else { for (;last<=c;last++) SET_LIST_BIT(last); } } else if (had_mbchar == 2) { set_list_bits(last, c, b); } else { /* restriction: range between sbc and mbc */ goto invalid_pattern; } } else if (p[0] == '-' && p[1] != ']') { last = c; PATFETCH_RAW(c1); range = 1; goto range_retry; } else { if (TRANSLATE_P() && c < 0x100) c = (unsigned char)translate[c]; if (had_mbchar == 0 && (!current_mbctype || !had_num_literal)) { SET_LIST_BIT(c); had_num_literal = 0; } else { set_list_bits(c, c, b); } } had_mbchar = 0; } /* Discard any character set/class bitmap bytes that are all 0 at the end of the map. Decrement the map-length byte too. */ while ((int)b[-1] > 0 && b[b[-1] - 1] == 0) b[-1]--; if (b[-1] != (1 << BYTEWIDTH) / BYTEWIDTH) memmove(&b[(unsigned char)b[-1]], &b[(1 << BYTEWIDTH) / BYTEWIDTH], 2 + EXTRACT_UNSIGNED(&b[(1 << BYTEWIDTH) / BYTEWIDTH])*8); b += b[-1] + 2 + EXTRACT_UNSIGNED(&b[(unsigned char)b[-1]])*8; had_num_literal = 0; break; case '(': { int old_options = options; int push_option = 0; int casefold = 0; PATFETCH(c); if (c == '?') { int negative = 0; PATFETCH_RAW(c); switch (c) { case 'x': case 'm': case 'i': case '-': for (;;) { switch (c) { case '-': negative = 1; break; case ':': case ')': break; case 'x': if (negative) options &= ~RE_OPTION_EXTENDED; else options |= RE_OPTION_EXTENDED; break; case 'm': if (negative) { if (options&RE_OPTION_MULTILINE) { options &= ~RE_OPTION_MULTILINE; } } else if (!(options&RE_OPTION_MULTILINE)) { options |= RE_OPTION_MULTILINE; } push_option = 1; break; case 'i': if (negative) { if (options&RE_OPTION_IGNORECASE) { options &= ~RE_OPTION_IGNORECASE; } } else if (!(options&RE_OPTION_IGNORECASE)) { options |= RE_OPTION_IGNORECASE; } casefold = 1; break; default: FREE_AND_RETURN(stackb, "undefined (?...) inline option"); } if (c == ')') { c = '#'; /* read whole in-line options */ break; } if (c == ':') break; PATFETCH_RAW(c); } break; case '#': for (;;) { PATFETCH(c); if (c == ')') break; } c = '#'; break; case ':': case '=': case '!': case '>': break; default: FREE_AND_RETURN(stackb, "undefined (?...) sequence"); } } else { PATUNFETCH; c = '('; } if (c == '#') { if (push_option) { BUFPUSH(option_set); BUFPUSH(options); } if (casefold) { if (options & RE_OPTION_IGNORECASE) BUFPUSH(casefold_on); else BUFPUSH(casefold_off); } break; } if (stackp+8 >= stacke) { DOUBLE_STACK(int); } /* Laststart should point to the start_memory that we are about to push (unless the pattern has RE_NREGS or more ('s). */ /* obsolete: now RE_NREGS is just a default register size. */ *stackp++ = b - bufp->buffer; *stackp++ = fixup_alt_jump ? fixup_alt_jump - bufp->buffer + 1 : 0; *stackp++ = begalt - bufp->buffer; switch (c) { case '(': BUFPUSH(start_memory); BUFPUSH(regnum); *stackp++ = regnum++; *stackp++ = b - bufp->buffer; BUFPUSH(0); /* too many ()'s to fit in a byte. (max 254) */ if (regnum >= RE_REG_MAX) goto too_big; break; case '=': case '!': case '>': BUFPUSH(start_nowidth); *stackp++ = b - bufp->buffer; BUFPUSH(0); /* temporary value */ BUFPUSH(0); if (c != '!') break; BUFPUSH(on_failure_jump); *stackp++ = b - bufp->buffer; BUFPUSH(0); /* temporary value */ BUFPUSH(0); break; case ':': BUFPUSH(start_paren); pending_exact = 0; default: break; } if (push_option) { BUFPUSH(option_set); BUFPUSH(options); } if (casefold) { if (options & RE_OPTION_IGNORECASE) BUFPUSH(casefold_on); else BUFPUSH(casefold_off); } *stackp++ = c; *stackp++ = old_options; fixup_alt_jump = 0; laststart = 0; begalt = b; } break; case ')': if (stackp == stackb) FREE_AND_RETURN(stackb, "unmatched )"); pending_exact = 0; if (fixup_alt_jump) { /* Push a dummy failure point at the end of the alternative for a possible future `finalize_jump' to pop. See comments at `push_dummy_failure' in `re_match'. */ BUFPUSH(push_dummy_failure); /* We allocated space for this jump when we assigned to `fixup_alt_jump', in the `handle_alt' case below. */ store_jump(fixup_alt_jump, jump, b); } if (options != stackp[-1]) { if ((options ^ stackp[-1]) & RE_OPTION_IGNORECASE) { BUFPUSH((options&RE_OPTION_IGNORECASE)?casefold_off:casefold_on); } if ((options ^ stackp[-1]) != RE_OPTION_IGNORECASE) { BUFPUSH(option_set); BUFPUSH(stackp[-1]); } } p0 = b; options = *--stackp; switch (c = *--stackp) { case '(': { char *loc = bufp->buffer + *--stackp; *loc = regnum - stackp[-1]; BUFPUSH(stop_memory); BUFPUSH(stackp[-1]); BUFPUSH(regnum - stackp[-1]); stackp--; } break; case '!': BUFPUSH(pop_and_fail); /* back patch */ STORE_NUMBER(bufp->buffer+stackp[-1], b - bufp->buffer - stackp[-1] - 2); stackp--; /* fall through */ case '=': BUFPUSH(stop_nowidth); /* tell stack-pos place to start_nowidth */ STORE_NUMBER(bufp->buffer+stackp[-1], b - bufp->buffer - stackp[-1] - 2); BUFPUSH(0); /* space to hold stack pos */ BUFPUSH(0); stackp--; break; case '>': BUFPUSH(stop_backtrack); /* tell stack-pos place to start_nowidth */ STORE_NUMBER(bufp->buffer+stackp[-1], b - bufp->buffer - stackp[-1] - 2); BUFPUSH(0); /* space to hold stack pos */ BUFPUSH(0); stackp--; break; case ':': BUFPUSH(stop_paren); break; default: break; } begalt = *--stackp + bufp->buffer; stackp--; fixup_alt_jump = *stackp ? *stackp + bufp->buffer - 1 : 0; laststart = *--stackp + bufp->buffer; if (c == '!' || c == '=') laststart = b; break; case '|': /* Insert before the previous alternative a jump which jumps to this alternative if the former fails. */ GET_BUFFER_SPACE(3); insert_jump(on_failure_jump, begalt, b + 6, b); pending_exact = 0; b += 3; /* The alternative before this one has a jump after it which gets executed if it gets matched. Adjust that jump so it will jump to this alternative's analogous jump (put in below, which in turn will jump to the next (if any) alternative's such jump, etc.). The last such jump jumps to the correct final destination. A picture: _____ _____ | | | | | v | v a | b | c If we are at `b', then fixup_alt_jump right now points to a three-byte space after `a'. We'll put in the jump, set fixup_alt_jump to right after `b', and leave behind three bytes which we'll fill in when we get to after `c'. */ if (fixup_alt_jump) store_jump(fixup_alt_jump, jump_past_alt, b); /* Mark and leave space for a jump after this alternative, to be filled in later either by next alternative or when know we're at the end of a series of alternatives. */ fixup_alt_jump = b; GET_BUFFER_SPACE(3); b += 3; laststart = 0; begalt = b; break; case '{': /* If there is no previous pattern, this is an invalid pattern. */ if (!laststart) { snprintf(error_msg, ERROR_MSG_MAX_SIZE, "invalid regular expression; there's no previous pattern, to which '{' would define cardinality at %d", p-pattern); FREE_AND_RETURN(stackb, error_msg); } if( p == pend) FREE_AND_RETURN(stackb, "invalid regular expression; '{' can't be last character" ); beg_interval = p - 1; lower_bound = -1; /* So can see if are set. */ upper_bound = -1; GET_UNSIGNED_NUMBER(lower_bound); if (c == ',') { GET_UNSIGNED_NUMBER(upper_bound); } else /* Interval such as `{1}' => match exactly once. */ upper_bound = lower_bound; if (lower_bound < 0 || c != '}') goto unfetch_interval; if (lower_bound >= RE_DUP_MAX || upper_bound >= RE_DUP_MAX) FREE_AND_RETURN(stackb, "too big quantifier in {,}"); if (upper_bound < 0) upper_bound = RE_DUP_MAX; if (lower_bound > upper_bound) FREE_AND_RETURN(stackb, "can't do {n,m} with n > m"); beg_interval = 0; pending_exact = 0; greedy = 1; if (p != pend) { PATFETCH(c); if (c == '?') greedy = 0; else PATUNFETCH; } if (lower_bound == 0) { zero_times_ok = 1; if (upper_bound == RE_DUP_MAX) { many_times_ok = 1; goto repeat; } if (upper_bound == 1) { many_times_ok = 0; goto repeat; } } if (lower_bound == 1) { if (upper_bound == 1) { /* No need to repeat */ break; } if (upper_bound == RE_DUP_MAX) { many_times_ok = 1; zero_times_ok = 0; goto repeat; } } /* If upper_bound is zero, don't want to succeed at all; jump from laststart to b + 3, which will be the end of the buffer after this jump is inserted. */ if (upper_bound == 0) { GET_BUFFER_SPACE(3); insert_jump(jump, laststart, b + 3, b); b += 3; break; } /* If lower_bound == upper_bound, repeat count can be removed */ if (lower_bound == upper_bound) { int mcnt; int skip_stop_paren = 0; if (b[-1] == stop_paren) { skip_stop_paren = 1; b--; } if (*laststart == exactn && laststart[1]+2 == b - laststart && laststart[1]*lower_bound < 256) { mcnt = laststart[1]; GET_BUFFER_SPACE((lower_bound-1)*mcnt); laststart[1] = lower_bound*mcnt; while (--lower_bound) { memcpy(b, laststart+2, mcnt); b += mcnt; } if (skip_stop_paren) BUFPUSH(stop_paren); break; } if (lower_bound < 5 && b - laststart < 10) { /* 5 and 10 are the magic numbers */ mcnt = b - laststart; GET_BUFFER_SPACE((lower_bound-1)*mcnt); while (--lower_bound) { memcpy(b, laststart, mcnt); b += mcnt; } if (skip_stop_paren) BUFPUSH(stop_paren); break; } if (skip_stop_paren) b++; /* push back stop_paren */ } /* Otherwise, we have a nontrivial interval. When we're all done, the pattern will look like: set_number_at set_number_at succeed_n jump_n (The upper bound and `jump_n' are omitted if `upper_bound' is 1, though.) */ { /* If the upper bound is > 1, we need to insert more at the end of the loop. */ unsigned nbytes = upper_bound == 1 ? 10 : 20; GET_BUFFER_SPACE(nbytes); /* Initialize lower bound of the `succeed_n', even though it will be set during matching by its attendant `set_number_at' (inserted next), because `re_compile_fastmap' needs to know. Jump to the `jump_n' we might insert below. */ insert_jump_n(succeed_n, laststart, b + (nbytes/2), b, lower_bound); b += 5; /* Just increment for the succeed_n here. */ /* Code to initialize the lower bound. Insert before the `succeed_n'. The `5' is the last two bytes of this `set_number_at', plus 3 bytes of the following `succeed_n'. */ insert_op_2(set_number_at, laststart, b, 5, lower_bound); b += 5; if (upper_bound > 1) { /* More than one repetition is allowed, so append a backward jump to the `succeed_n' that starts this interval. When we've reached this during matching, we'll have matched the interval once, so jump back only `upper_bound - 1' times. */ GET_BUFFER_SPACE(5); store_jump_n(b, greedy?jump_n:finalize_push_n, laststart + 5, upper_bound - 1); b += 5; /* The location we want to set is the second parameter of the `jump_n'; that is `b-2' as an absolute address. `laststart' will be the `set_number_at' we're about to insert; `laststart+3' the number to set, the source for the relative address. But we are inserting into the middle of the pattern -- so everything is getting moved up by 5. Conclusion: (b - 2) - (laststart + 3) + 5, i.e., b - laststart. We insert this at the beginning of the loop so that if we fail during matching, we'll reinitialize the bounds. */ insert_op_2(set_number_at, laststart, b, b - laststart, upper_bound - 1); b += 5; } } break; unfetch_interval: /* If an invalid interval, match the characters as literals. */ re_warning("regexp has invalid interval"); p = beg_interval; beg_interval = 0; /* normal_char and normal_backslash need `c'. */ PATFETCH(c); goto normal_char; case '\\': if (p == pend) FREE_AND_RETURN(stackb, "invalid regular expression; '\\' can't be last character"); /* Do not translate the character after the \, so that we can distinguish, e.g., \B from \b, even if we normally would translate, e.g., B to b. */ PATFETCH_RAW(c); switch (c) { case 's': case 'S': case 'd': case 'D': while (b - bufp->buffer + 9 + (1 << BYTEWIDTH) / BYTEWIDTH > bufp->allocated) EXTEND_BUFFER; laststart = b; if (c == 's' || c == 'd') { BUFPUSH(charset); } else { BUFPUSH(charset_not); } BUFPUSH((1 << BYTEWIDTH) / BYTEWIDTH); memset(b, 0, (1 << BYTEWIDTH) / BYTEWIDTH + 2); if (c == 's' || c == 'S') { SET_LIST_BIT(' '); SET_LIST_BIT('\t'); SET_LIST_BIT('\n'); SET_LIST_BIT('\r'); SET_LIST_BIT('\f'); } else { char cc; for (cc = '0'; cc <= '9'; cc++) { SET_LIST_BIT(cc); } } while ((int)b[-1] > 0 && b[b[-1] - 1] == 0) b[-1]--; if (b[-1] != (1 << BYTEWIDTH) / BYTEWIDTH) memmove(&b[(unsigned char)b[-1]], &b[(1 << BYTEWIDTH) / BYTEWIDTH], 2 + EXTRACT_UNSIGNED(&b[(1 << BYTEWIDTH) / BYTEWIDTH])*8); b += b[-1] + 2 + EXTRACT_UNSIGNED(&b[(unsigned char)b[-1]])*8; break; case 'w': laststart = b; BUFPUSH(wordchar); break; case 'W': laststart = b; BUFPUSH(notwordchar); break; #ifndef RUBY case '<': BUFPUSH(wordbeg); break; case '>': BUFPUSH(wordend); break; #endif case 'b': BUFPUSH(wordbound); break; case 'B': BUFPUSH(notwordbound); break; case 'A': BUFPUSH(begbuf); break; case 'Z': if ((bufp->options & RE_OPTION_SINGLELINE) == 0) { BUFPUSH(endbuf2); break; } /* fall through */ case 'z': BUFPUSH(endbuf); break; case 'G': BUFPUSH(begpos); break; /* hex */ case 'x': had_mbchar = 0; c = scan_hex(p, 2, &numlen); if (numlen == 0) goto invalid_escape; p += numlen; had_num_literal = 1; goto numeric_char; /* octal */ case '0': had_mbchar = 0; c = scan_oct(p, 2, &numlen); p += numlen; had_num_literal = 1; goto numeric_char; /* back-ref or octal */ case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': PATUNFETCH; p0 = p; had_mbchar = 0; c1 = 0; GET_UNSIGNED_NUMBER(c1); if (!ISDIGIT(c)) PATUNFETCH; if (9 < c1 && c1 >= regnum) { /* need to get octal */ c = scan_oct(p0, 3, &numlen) & 0xff; p = p0 + numlen; c1 = 0; had_num_literal = 1; goto numeric_char; } laststart = b; BUFPUSH(duplicate); BUFPUSH(c1); break; case 'M': case 'C': case 'c': p0 = --p; c = read_special(p, pend, &p0); if (c > 255) goto invalid_escape; p = p0; had_num_literal = 1; goto numeric_char; default: c = read_backslash(c); goto normal_char; } break; case '#': if (options & RE_OPTION_EXTENDED) { while (p != pend) { PATFETCH(c); if (c == '\n') break; } break; } goto normal_char; case ' ': case '\t': case '\f': case '\r': case '\n': if (options & RE_OPTION_EXTENDED) break; default: if (c == ']') re_warning("regexp has `]' without escape"); else if (c == '}') re_warning("regexp has `}' without escape"); normal_char: /* Expects the character in `c'. */ had_mbchar = 0; if (ismbchar(c)) { had_mbchar = 1; c1 = p - pattern; } numeric_char: nextp = p + mbclen(c) - 1; if (!pending_exact || pending_exact + *pending_exact + 1 != b || *pending_exact >= (c1 ? 0176 : 0177) || (nextp < pend && ( *nextp == '+' || *nextp == '?' || *nextp == '*' || *nextp == '^' || *nextp == '{'))) { laststart = b; BUFPUSH(exactn); pending_exact = b; BUFPUSH(0); } if (had_num_literal || c == 0xff) { BUFPUSH(0xff); (*pending_exact)++; had_num_literal = 0; } BUFPUSH(c); (*pending_exact)++; if (had_mbchar) { int len = mbclen(c) - 1; while (len--) { PATFETCH_RAW(c); BUFPUSH(c); (*pending_exact)++; } } } } if (fixup_alt_jump) store_jump(fixup_alt_jump, jump, b); if (stackp != stackb) FREE_AND_RETURN(stackb, "unmatched ("); /* set optimize flags */ laststart = bufp->buffer; if (laststart != b) { if (*laststart == dummy_failure_jump) laststart += 3; else if (*laststart == try_next) laststart += 3; if (*laststart == anychar_repeat) { bufp->options |= RE_OPTIMIZE_ANCHOR; } } bufp->used = b - bufp->buffer; bufp->re_nsub = regnum; laststart = bufp->buffer; if (laststart != b) { if (*laststart == start_memory) laststart += 3; if (*laststart == exactn) { bufp->options |= RE_OPTIMIZE_EXACTN; bufp->must = laststart+1; } } if (!bufp->must) { bufp->must = calculate_must_string(bufp->buffer, b); } if (current_mbctype == MBCTYPE_SJIS) bufp->options |= RE_OPTIMIZE_NO_BM; else if (bufp->must) { int i; int len = (unsigned char)bufp->must[0]; for (i=1; imust[i] == 0xff || (current_mbctype && ismbchar(bufp->must[i]))) { bufp->options |= RE_OPTIMIZE_NO_BM; break; } } if (!(bufp->options & RE_OPTIMIZE_NO_BM)) { bufp->must_skip = (int *) xmalloc((1 << BYTEWIDTH)*sizeof(int)); bm_init_skip(bufp->must_skip, (unsigned char*)bufp->must+1, (unsigned char)bufp->must[0], (unsigned char*)(MAY_TRANSLATE()?translate:0)); } } bufp->regstart = TMALLOC(regnum, unsigned char*); bufp->regend = TMALLOC(regnum, unsigned char*); bufp->old_regstart = TMALLOC(regnum, unsigned char*); bufp->old_regend = TMALLOC(regnum, unsigned char*); bufp->reg_info = TMALLOC(regnum, register_info_type); bufp->best_regstart = TMALLOC(regnum, unsigned char*); bufp->best_regend = TMALLOC(regnum, unsigned char*); FREE_AND_RETURN(stackb, 0); invalid_pattern: FREE_AND_RETURN(stackb, "invalid regular expression"); end_of_pattern: FREE_AND_RETURN(stackb, "premature end of regular expression"); too_big: FREE_AND_RETURN(stackb, "regular expression too big"); memory_exhausted: FREE_AND_RETURN(stackb, "memory exhausted"); nested_meta: FREE_AND_RETURN(stackb, "nested *?+ in regexp"); invalid_escape: FREE_AND_RETURN(stackb, "Invalid escape character syntax"); } void re_free_pattern(bufp) struct re_pattern_buffer *bufp; { xfree(bufp->buffer); xfree(bufp->fastmap); if (bufp->must_skip) xfree(bufp->must_skip); xfree(bufp->regstart); xfree(bufp->regend); xfree(bufp->old_regstart); xfree(bufp->old_regend); xfree(bufp->best_regstart); xfree(bufp->best_regend); xfree(bufp->reg_info); xfree(bufp); } /* Store a jump of the form . Store in the location FROM a jump operation to jump to relative address FROM - TO. OPCODE is the opcode to store. */ static void store_jump(from, opcode, to) char *from, *to; int opcode; { from[0] = (char)opcode; STORE_NUMBER(from + 1, to - (from + 3)); } /* Open up space before char FROM, and insert there a jump to TO. CURRENT_END gives the end of the storage not in use, so we know how much data to copy up. OP is the opcode of the jump to insert. If you call this function, you must zero out pending_exact. */ static void insert_jump(op, from, to, current_end) int op; char *from, *to, *current_end; { register char *pfrom = current_end; /* Copy from here... */ register char *pto = current_end + 3; /* ...to here. */ while (pfrom != from) *--pto = *--pfrom; store_jump(from, op, to); } /* Store a jump of the form . Store in the location FROM a jump operation to jump to relative address FROM - TO. OPCODE is the opcode to store, N is a number the jump uses, say, to decide how many times to jump. If you call this function, you must zero out pending_exact. */ static void store_jump_n(from, opcode, to, n) char *from, *to; int opcode; unsigned n; { from[0] = (char)opcode; STORE_NUMBER(from + 1, to - (from + 3)); STORE_NUMBER(from + 3, n); } /* Similar to insert_jump, but handles a jump which needs an extra number to handle minimum and maximum cases. Open up space at location FROM, and insert there a jump to TO. CURRENT_END gives the end of the storage in use, so we know how much data to copy up. OP is the opcode of the jump to insert. If you call this function, you must zero out pending_exact. */ static void insert_jump_n(op, from, to, current_end, n) int op; char *from, *to, *current_end; unsigned n; { register char *pfrom = current_end; /* Copy from here... */ register char *pto = current_end + 5; /* ...to here. */ while (pfrom != from) *--pto = *--pfrom; store_jump_n(from, op, to, n); } /* Open up space at location THERE, and insert operation OP. CURRENT_END gives the end of the storage in use, so we know how much data to copy up. If you call this function, you must zero out pending_exact. */ static void insert_op(op, there, current_end) int op; char *there, *current_end; { register char *pfrom = current_end; /* Copy from here... */ register char *pto = current_end + 1; /* ...to here. */ while (pfrom != there) *--pto = *--pfrom; there[0] = (char)op; } /* Open up space at location THERE, and insert operation OP followed by NUM_1 and NUM_2. CURRENT_END gives the end of the storage in use, so we know how much data to copy up. If you call this function, you must zero out pending_exact. */ static void insert_op_2(op, there, current_end, num_1, num_2) int op; char *there, *current_end; int num_1, num_2; { register char *pfrom = current_end; /* Copy from here... */ register char *pto = current_end + 5; /* ...to here. */ while (pfrom != there) *--pto = *--pfrom; there[0] = (char)op; STORE_NUMBER(there + 1, num_1); STORE_NUMBER(there + 3, num_2); } #define trans_eq(c1, c2, translate) (translate?(translate[c1]==translate[c2]):((c1)==(c2))) static int slow_match(little, lend, big, bend, translate) const unsigned char *little, *lend; const unsigned char *big, *bend; const unsigned char *translate; { int c; while (little < lend && big < bend) { c = *little++; if (c == 0xff) c = *little++; if (!trans_eq(*big++, c, translate)) break; } if (little == lend) return 1; return 0; } static int slow_search(little, llen, big, blen, translate) const unsigned char *little; int llen; const unsigned char *big; int blen; const char *translate; { const unsigned char *bsave = big; const unsigned char *bend = big + blen; register int c; int fescape = 0; c = *little; if (c == 0xff) { c = little[1]; fescape = 1; } else if (translate && !ismbchar(c)) { c = translate[c]; } while (big < bend) { /* look for first character */ if (fescape) { while (big < bend) { if (*big == c) break; big++; } } else if (translate && !ismbchar(c)) { while (big < bend) { if (ismbchar(*big)) big+=mbclen(*big)-1; else if (translate[*big] == c) break; big++; } } else { while (big < bend) { if (*big == c) break; if (ismbchar(*big)) big+=mbclen(*big)-1; big++; } } if (slow_match(little, little+llen, big, bend, (unsigned char *)translate)) return big - bsave; big+=mbclen(*big); } return -1; } static void bm_init_skip(skip, pat, m, translate) int *skip; unsigned char *pat; int m; const unsigned char *translate; { int j, c; for (c=0; c<256; c++) { skip[c] = m; } if (translate) { for (j=0; j= 0 && translate[big[k]] == translate[little[j]]) { k--; j--; } if (j < 0) return k+1; i += skip[translate[big[i]]]; } return -1; } while (i < blen) { k = i; j = llen-1; while (j >= 0 && big[k] == little[j]) { k--; j--; } if (j < 0) return k+1; i += skip[big[i]]; } return -1; } /* Given a pattern, compute a fastmap from it. The fastmap records which of the (1 << BYTEWIDTH) possible characters can start a string that matches the pattern. This fastmap is used by re_search to skip quickly over totally implausible text. The caller must supply the address of a (1 << BYTEWIDTH)-byte data area as bufp->fastmap. The other components of bufp describe the pattern to be used. */ static int re_compile_fastmap0(bufp) struct re_pattern_buffer *bufp; { unsigned char *pattern = (unsigned char*)bufp->buffer; int size = bufp->used; register char *fastmap = bufp->fastmap; register unsigned char *p = pattern; register unsigned char *pend = pattern + size; register int j, k; unsigned is_a_succeed_n; unsigned char *stacka[NFAILURES]; unsigned char **stackb = stacka; unsigned char **stackp = stackb; unsigned char **stacke = stackb + NFAILURES; int options = bufp->options; memset(fastmap, 0, (1 << BYTEWIDTH)); bufp->fastmap_accurate = 1; bufp->can_be_null = 0; while (p) { is_a_succeed_n = 0; if (p == pend) { bufp->can_be_null = 1; break; } #ifdef SWITCH_ENUM_BUG switch ((int)((enum regexpcode)*p++)) #else switch ((enum regexpcode)*p++) #endif { case exactn: if (p[1] == 0xff) { if (TRANSLATE_P()) fastmap[translate[p[2]]] = 2; else fastmap[p[2]] = 2; bufp->options |= RE_OPTIMIZE_BMATCH; } else if (TRANSLATE_P()) fastmap[translate[p[1]]] = 1; else fastmap[p[1]] = 1; break; case begline: case begbuf: case begpos: case endbuf: case endbuf2: case wordbound: case notwordbound: case wordbeg: case wordend: case pop_and_fail: case push_dummy_failure: case start_paren: case stop_paren: continue; case casefold_on: bufp->options |= RE_MAY_IGNORECASE; options |= RE_OPTION_IGNORECASE; continue; case casefold_off: options &= ~RE_OPTION_IGNORECASE; continue; case option_set: options = *p++; continue; case endline: if (TRANSLATE_P()) fastmap[translate['\n']] = 1; else fastmap['\n'] = 1; if ((options & RE_OPTION_SINGLELINE) == 0 && bufp->can_be_null == 0) bufp->can_be_null = 2; break; case jump_n: case finalize_jump: case maybe_finalize_jump: case jump: case jump_past_alt: case dummy_failure_jump: case finalize_push: case finalize_push_n: EXTRACT_NUMBER_AND_INCR(j, p); p += j; if (j > 0) continue; /* Jump backward reached implies we just went through the body of a loop and matched nothing. Opcode jumped to should be an on_failure_jump. Just treat it like an ordinary jump. For a * loop, it has pushed its failure point already; If so, discard that as redundant. */ if ((enum regexpcode)*p != on_failure_jump && (enum regexpcode)*p != try_next && (enum regexpcode)*p != succeed_n) continue; p++; EXTRACT_NUMBER_AND_INCR(j, p); p += j; if (stackp != stackb && *stackp == p) stackp--; /* pop */ continue; case try_next: case start_nowidth: case stop_nowidth: case stop_backtrack: p += 2; continue; case succeed_n: is_a_succeed_n = 1; /* Get to the number of times to succeed. */ EXTRACT_NUMBER(k, p + 2); /* Increment p past the n for when k != 0. */ if (k != 0) { p += 4; continue; } /* fall through */ case on_failure_jump: EXTRACT_NUMBER_AND_INCR(j, p); if (p + j < pend) { if (stackp == stacke) { EXPAND_FAIL_STACK(); } *++stackp = p + j; /* push */ } else { bufp->can_be_null = 1; } if (is_a_succeed_n) EXTRACT_NUMBER_AND_INCR(k, p); /* Skip the n. */ continue; case set_number_at: p += 4; continue; case start_memory: case stop_memory: p += 2; continue; case duplicate: bufp->can_be_null = 1; if (*p >= bufp->re_nsub) break; fastmap['\n'] = 1; case anychar_repeat: case anychar: for (j = 0; j < (1 << BYTEWIDTH); j++) { if (j != '\n' || (options & RE_OPTION_MULTILINE)) fastmap[j] = 1; } if (bufp->can_be_null) { FREE_AND_RETURN(stackb, 0); } /* Don't return; check the alternative paths so we can set can_be_null if appropriate. */ if ((enum regexpcode)p[-1] == anychar_repeat) { continue; } break; case wordchar: for (j = 0; j < 0x80; j++) { if (SYNTAX(j) == Sword) fastmap[j] = 1; } switch (current_mbctype) { case MBCTYPE_ASCII: for (j = 0x80; j < (1 << BYTEWIDTH); j++) { if (SYNTAX(j) == Sword2) fastmap[j] = 1; } break; case MBCTYPE_EUC: case MBCTYPE_SJIS: case MBCTYPE_UTF8: for (j = 0x80; j < (1 << BYTEWIDTH); j++) { if (re_mbctab[j]) fastmap[j] = 1; } break; } break; case notwordchar: for (j = 0; j < 0x80; j++) if (SYNTAX(j) != Sword) fastmap[j] = 1; switch (current_mbctype) { case MBCTYPE_ASCII: for (j = 0x80; j < (1 << BYTEWIDTH); j++) { if (SYNTAX(j) != Sword2) fastmap[j] = 1; } break; case MBCTYPE_EUC: case MBCTYPE_SJIS: case MBCTYPE_UTF8: for (j = 0x80; j < (1 << BYTEWIDTH); j++) { if (!re_mbctab[j]) fastmap[j] = 1; } break; } break; case charset: /* NOTE: Charset for single-byte chars never contain multi-byte char. See set_list_bits(). */ for (j = *p++ * BYTEWIDTH - 1; j >= 0; j--) if (p[j / BYTEWIDTH] & (1 << (j % BYTEWIDTH))) { int tmp = TRANSLATE_P()?translate[j]:j; fastmap[tmp] = 1; } { unsigned short size; unsigned long c, beg, end; p += p[-1] + 2; size = EXTRACT_UNSIGNED(&p[-2]); for (j = 0; j < (int)size; j++) { c = EXTRACT_MBC(&p[j*8]); beg = WC2MBC1ST(c); c = EXTRACT_MBC(&p[j*8+4]); end = WC2MBC1ST(c); /* set bits for 1st bytes of multi-byte chars. */ while (beg <= end) { /* NOTE: Charset for multi-byte chars might contain single-byte chars. We must reject them. */ if (c < 0x100) { fastmap[beg] = 2; bufp->options |= RE_OPTIMIZE_BMATCH; } else if (ismbchar(beg)) fastmap[beg] = 1; beg++; } } } break; case charset_not: /* S: set of all single-byte chars. M: set of all first bytes that can start multi-byte chars. s: any set of single-byte chars. m: any set of first bytes that can start multi-byte chars. We assume S+M = U. ___ _ _ s+m = (S*s+M*m). */ /* Chars beyond end of map must be allowed */ /* NOTE: Charset_not for single-byte chars might contain multi-byte chars. See set_list_bits(). */ for (j = *p * BYTEWIDTH; j < (1 << BYTEWIDTH); j++) if (!ismbchar(j)) fastmap[j] = 1; for (j = *p++ * BYTEWIDTH - 1; j >= 0; j--) if (!(p[j / BYTEWIDTH] & (1 << (j % BYTEWIDTH)))) { if (!ismbchar(j)) fastmap[j] = 1; } { unsigned short size; unsigned long c, beg; int num_literal = 0; p += p[-1] + 2; size = EXTRACT_UNSIGNED(&p[-2]); if (size == 0) { for (j = 0x80; j < (1 << BYTEWIDTH); j++) if (ismbchar(j)) fastmap[j] = 1; break; } for (j = 0,c = 0;j < (int)size; j++) { unsigned int cc = EXTRACT_MBC(&p[j*8]); beg = WC2MBC1ST(cc); while (c <= beg) { if (ismbchar(c)) fastmap[c] = 1; c++; } cc = EXTRACT_MBC(&p[j*8+4]); if (cc < 0xff) { num_literal = 1; while (c <= cc) { if (ismbchar(c)) fastmap[c] = 1; c++; } } c = WC2MBC1ST(cc); } for (j = c; j < (1 << BYTEWIDTH); j++) { if (num_literal) fastmap[j] = 1; if (ismbchar(j)) fastmap[j] = 1; } } break; case unused: /* pacify gcc -Wall */ break; } /* Get here means we have successfully found the possible starting characters of one path of the pattern. We need not follow this path any farther. Instead, look at the next alternative remembered in the stack. */ if (stackp != stackb) p = *stackp--; /* pop */ else break; } FREE_AND_RETURN(stackb, 0); memory_exhausted: FREE_AND_RETURN(stackb, -2); } void re_compile_fastmap(bufp) struct re_pattern_buffer *bufp; { (void)re_compile_fastmap0(bufp); } /* adjust startpos value to the position between characters. */ int re_mbc_startpos(string, size, startpos, range) const char *string; int size, startpos, range; { int i = mbc_startpos(string, startpos); if (i < startpos) { if (range > 0) { startpos = i + mbclen(string[i]); } else { int len = mbclen(string[i]); if (i + len <= startpos) startpos = i + len; else startpos = i; } } return startpos; } int re_adjust_startpos(bufp, string, size, startpos, range) struct re_pattern_buffer *bufp; const char *string; int size, startpos, range; { /* Update the fastmap now if not correct already. */ if (!bufp->fastmap_accurate) { int ret = re_compile_fastmap0(bufp); if (ret) return ret; } /* Adjust startpos for mbc string */ if (current_mbctype && startpos>0 && !(bufp->options&RE_OPTIMIZE_BMATCH)) { startpos = re_mbc_startpos(string, size, startpos, range); } return startpos; } static int re_match_exec _((struct re_pattern_buffer *, const char *, int, int, int, struct re_registers *)); /* Using the compiled pattern in BUFP->buffer, first tries to match STRING, starting first at index STARTPOS, then at STARTPOS + 1, and so on. RANGE is the number of places to try before giving up. If RANGE is negative, it searches backwards, i.e., the starting positions tried are STARTPOS, STARTPOS - 1, etc. STRING is of SIZE. In REGS, return the indices of STRING that matched the entire BUFP->buffer and its contained subexpressions. The value returned is the position in the strings at which the match was found, or -1 if no match was found, or -2 if error (such as failure stack overflow). */ int re_search(bufp, string, size, startpos, range, regs) struct re_pattern_buffer *bufp; const char *string; int size, startpos, range; struct re_registers *regs; { register char *fastmap = bufp->fastmap; int val, anchor = 0, initpos = startpos; /* Check for out-of-range starting position. */ if (startpos < 0 || startpos > size) return -1; if (!string) { if (size == 0) string = ""; else return -1; } /* Update the fastmap now if not correct already. */ if (fastmap && !bufp->fastmap_accurate) { int ret = re_compile_fastmap0(bufp); if (ret) return ret; } /* If the search isn't to be a backwards one, don't waste time in a search for a pattern that must be anchored. */ if (bufp->used > 0) { switch ((enum regexpcode)bufp->buffer[0]) { case begbuf: begbuf_match: if (range > 0) { if (startpos > 0) return -1; else { val = re_match(bufp, string, size, 0, regs); if (val >= 0) return 0; return val; } } break; case begline: anchor = 1; break; case begpos: val = re_match(bufp, string, size, startpos, regs); if (val >= 0) return startpos; return val; default: break; } } if (bufp->options & RE_OPTIMIZE_ANCHOR) { if (bufp->options&RE_OPTION_MULTILINE && range > 0) { goto begbuf_match; } anchor = 1; } if (bufp->must) { int len = ((unsigned char*)bufp->must)[0]; int pos, pbeg, pend; pbeg = startpos; pend = startpos + range; if (pbeg > pend) { /* swap pbeg,pend */ pos = pend; pend = pbeg; pbeg = pos; } pend = size; if (bufp->options & RE_OPTIMIZE_NO_BM) { pos = slow_search((unsigned char *)(bufp->must+1), len, (unsigned char*)(string+pbeg), pend-pbeg, (char *)(MAY_TRANSLATE()?translate:0)); } else { pos = bm_search((unsigned char *)(bufp->must+1), len, (unsigned char *)(string+pbeg), pend-pbeg, bufp->must_skip, MAY_TRANSLATE()?translate:0); } if (pos == -1) return -1; if (range > 0 && (bufp->options & RE_OPTIMIZE_EXACTN)) { startpos += pos; range -= pos; if (range < 0) return -1; } } for (;;) { /* If a fastmap is supplied, skip quickly over characters that cannot possibly be the start of a match. Note, however, that if the pattern can possibly match the null string, we must test it at each starting point so that we take the first null string we get. */ if (fastmap && startpos < size && bufp->can_be_null != 1 && !(anchor && startpos == 0)) { if (range > 0) { /* Searching forwards. */ register unsigned char *p, c; int irange = range; p = (unsigned char*)string+startpos; while (range > 0) { c = *p++; if (ismbchar(c)) { int len; if (fastmap[c]) break; len = mbclen(c) - 1; while (len--) { c = *p++; range--; if (fastmap[c] == 2) goto startpos_adjust; } } else { if (fastmap[MAY_TRANSLATE() ? translate[c] : c]) break; } range--; } startpos_adjust: startpos += irange - range; } else { /* Searching backwards. */ register unsigned char c; c = string[startpos]; c &= 0xff; if (MAY_TRANSLATE() ? !fastmap[translate[c]] : !fastmap[c]) goto advance; } } if (startpos > size) return -1; if ((anchor || !bufp->can_be_null) && range > 0 && size > 0 && startpos == size) return -1; val = re_match_exec(bufp, string, size, startpos, initpos, regs); if (val >= 0) return startpos; if (val == -2) return -2; #ifndef NO_ALLOCA #ifdef C_ALLOCA alloca(0); #endif /* C_ALLOCA */ #endif /* NO_ALLOCA */ if (range > 0) { if (anchor && startpos < size && (startpos < 1 || string[startpos-1] != '\n')) { while (range > 0 && string[startpos] != '\n') { range--; startpos++; } } } advance: if (!range) break; else if (range > 0) { const char *d = string + startpos; if (ismbchar(*d)) { int len = mbclen(*d) - 1; range-=len, startpos+=len; if (!range) break; } range--, startpos++; } else { range++, startpos--; { const char *s, *d, *p; s = string; d = string + startpos; for (p = d; p-- > s && ismbchar(*p); ) /* --p >= s would not work on 80[12]?86. (when the offset of s equals 0 other than huge model.) */ ; if (!((d - p) & 1)) { if (!range) break; range++, startpos--; } } } } return -1; } /* The following are used for re_match, defined below: */ /* Accessing macros used in re_match: */ #define IS_ACTIVE(R) ((R).bits.is_active) #define MATCHED_SOMETHING(R) ((R).bits.matched_something) /* Macros used by re_match: */ /* I.e., regstart, regend, and reg_info. */ #define NUM_REG_ITEMS 3 /* I.e., ptr and count. */ #define NUM_COUNT_ITEMS 2 /* Individual items aside from the registers. */ #define NUM_NONREG_ITEMS 4 /* We push at most this many things on the stack whenever we fail. The `+ 2' refers to PATTERN_PLACE and STRING_PLACE, which are arguments to the PUSH_FAILURE_POINT macro. */ #define MAX_NUM_FAILURE_ITEMS (num_regs * NUM_REG_ITEMS + NUM_NONREG_ITEMS) /* We push this many things on the stack whenever we fail. */ #define NUM_FAILURE_ITEMS (last_used_reg * NUM_REG_ITEMS + NUM_NONREG_ITEMS + 1) /* This pushes counter information for succeed_n and jump_n */ #define PUSH_FAILURE_COUNT(ptr) \ do { \ int c; \ EXTRACT_NUMBER(c, ptr); \ ENSURE_FAIL_STACK(NUM_COUNT_ITEMS); \ *stackp++ = (unsigned char*)(long)c; \ *stackp++ = (ptr); \ num_failure_counts++; \ } while (0) /* This pushes most of the information about the current state we will want if we ever fail back to it. */ #define PUSH_FAILURE_POINT(pattern_place, string_place) \ do { \ long last_used_reg, this_reg; \ \ /* Find out how many registers are active or have been matched. \ (Aside from register zero, which is only set at the end.) */ \ for (last_used_reg = num_regs-1; last_used_reg > 0; last_used_reg--)\ if (!REG_UNSET(regstart[last_used_reg])) \ break; \ \ ENSURE_FAIL_STACK(NUM_FAILURE_ITEMS); \ *stackp++ = (unsigned char*)(long)num_failure_counts; \ num_failure_counts = 0; \ \ /* Now push the info for each of those registers. */ \ for (this_reg = 1; this_reg <= last_used_reg; this_reg++) { \ *stackp++ = regstart[this_reg]; \ *stackp++ = regend[this_reg]; \ *stackp++ = reg_info[this_reg].word; \ } \ \ /* Push how many registers we saved. */ \ *stackp++ = (unsigned char*)last_used_reg; \ \ *stackp++ = pattern_place; \ *stackp++ = string_place; \ *stackp++ = (unsigned char*)(long)options; /* current option status */ \ *stackp++ = (unsigned char*)0; /* non-greedy flag */ \ } while(0) #define NON_GREEDY ((unsigned char*)1) #define POP_FAILURE_COUNT() \ do { \ unsigned char *ptr = *--stackp; \ int count = (long)*--stackp; \ STORE_NUMBER(ptr, count); \ } while (0) /* This pops what PUSH_FAILURE_POINT pushes. */ #define POP_FAILURE_POINT() \ do { \ long temp; \ stackp -= NUM_NONREG_ITEMS; /* Remove failure points (and flag). */ \ temp = (long)*--stackp; /* How many regs pushed. */ \ temp *= NUM_REG_ITEMS; /* How much to take off the stack. */ \ stackp -= temp; /* Remove the register info. */ \ temp = (long)*--stackp; /* How many counters pushed. */ \ while (temp--) { \ POP_FAILURE_COUNT(); /* Remove the counter info. */ \ } \ num_failure_counts = 0; /* Reset num_failure_counts. */ \ } while(0) /* Registers are set to a sentinel when they haven't yet matched. */ #define REG_UNSET_VALUE ((unsigned char*)-1) #define REG_UNSET(e) ((e) == REG_UNSET_VALUE) #define PREFETCH if (d == dend) goto fail /* Call this when have matched something; it sets `matched' flags for the registers corresponding to the subexpressions of which we currently are inside. */ #define SET_REGS_MATCHED \ do { unsigned this_reg; \ for (this_reg = 0; this_reg < num_regs; this_reg++) { \ if (IS_ACTIVE(reg_info[this_reg])) \ MATCHED_SOMETHING(reg_info[this_reg]) = 1; \ else \ MATCHED_SOMETHING(reg_info[this_reg]) = 0; \ } \ } while(0) #define AT_STRINGS_BEG(d) ((d) == string) #define AT_STRINGS_END(d) ((d) == dend) #define IS_A_LETTER(d) (SYNTAX(*(d)) == Sword || \ (current_mbctype ? \ (re_mbctab[*(d)] && ((d)+mbclen(*(d)))<=dend): \ SYNTAX(*(d)) == Sword2)) #define PREV_IS_A_LETTER(d) ((current_mbctype == MBCTYPE_SJIS)? \ IS_A_LETTER((d)-(!AT_STRINGS_BEG((d)-1)&& \ ismbchar((d)[-2])?2:1)): \ ((current_mbctype && ((d)[-1] >= 0x80)) || \ IS_A_LETTER((d)-1))) static void init_regs(regs, num_regs) struct re_registers *regs; unsigned int num_regs; { int i; regs->num_regs = num_regs; if (num_regs < RE_NREGS) num_regs = RE_NREGS; if (regs->allocated == 0) { regs->beg = TMALLOC(num_regs, int); regs->end = TMALLOC(num_regs, int); regs->allocated = num_regs; } else if (regs->allocated < num_regs) { TREALLOC(regs->beg, num_regs, int); TREALLOC(regs->end, num_regs, int); regs->allocated = num_regs; } for (i=0; ibeg[i] = regs->end[i] = -1; } } /* Match the pattern described by BUFP against STRING, which is of SIZE. Start the match at index POS in STRING. In REGS, return the indices of STRING that matched the entire BUFP->buffer and its contained subexpressions. If bufp->fastmap is nonzero, then it had better be up to date. The reason that the data to match are specified as two components which are to be regarded as concatenated is so this function can be used directly on the contents of an Emacs buffer. -1 is returned if there is no match. -2 is returned if there is an error (such as match stack overflow). Otherwise the value is the length of the substring which was matched. */ int re_match(bufp, string_arg, size, pos, regs) struct re_pattern_buffer *bufp; const char *string_arg; int size, pos; struct re_registers *regs; { return re_match_exec(bufp, string_arg, size, pos, pos, regs); } static int re_match_exec(bufp, string_arg, size, pos, beg, regs) struct re_pattern_buffer *bufp; const char *string_arg; int size, pos, beg; struct re_registers *regs; { register unsigned char *p = (unsigned char*)bufp->buffer; unsigned char *p1; /* Pointer to beyond end of buffer. */ register unsigned char *pend = p + bufp->used; unsigned num_regs = bufp->re_nsub; unsigned char *string = (unsigned char*)string_arg; register unsigned char *d, *dend; register int mcnt; /* Multipurpose. */ int options = bufp->options; /* Failure point stack. Each place that can handle a failure further down the line pushes a failure point on this stack. It consists of restart, regend, and reg_info for all registers corresponding to the subexpressions we're currently inside, plus the number of such registers, and, finally, two char *'s. The first char * is where to resume scanning the pattern; the second one is where to resume scanning the strings. If the latter is zero, the failure point is a ``dummy''; if a failure happens and the failure point is a dummy, it gets discarded and the next next one is tried. */ unsigned char **const stacka = 0; unsigned char **stackb; unsigned char **stackp; unsigned char **stacke; /* Information on the contents of registers. These are pointers into the input strings; they record just what was matched (on this attempt) by a subexpression part of the pattern, that is, the regnum-th regstart pointer points to where in the pattern we began matching and the regnum-th regend points to right after where we stopped matching the regnum-th subexpression. (The zeroth register keeps track of what the whole pattern matches.) */ unsigned char **regstart = bufp->regstart; unsigned char **regend = bufp->regend; /* If a group that's operated upon by a repetition operator fails to match anything, then the register for its start will need to be restored because it will have been set to wherever in the string we are when we last see its open-group operator. Similarly for a register's end. */ unsigned char **old_regstart = bufp->old_regstart; unsigned char **old_regend = bufp->old_regend; /* The is_active field of reg_info helps us keep track of which (possibly nested) subexpressions we are currently in. The matched_something field of reg_info[reg_num] helps us tell whether or not we have matched any of the pattern so far this time through the reg_num-th subexpression. These two fields get reset each time through any loop their register is in. */ register_info_type *reg_info = bufp->reg_info; /* The following record the register info as found in the above variables when we find a match better than any we've seen before. This happens as we backtrack through the failure points, which in turn happens only if we have not yet matched the entire string. */ unsigned best_regs_set = 0; unsigned char **best_regstart = bufp->best_regstart; unsigned char **best_regend = bufp->best_regend; int num_failure_counts = 0; if (regs) { init_regs(regs, num_regs); } /* Initialize the stack. */ stackb = TMALLOC(MAX_NUM_FAILURE_ITEMS * NFAILURES, unsigned char*); stackp = stackb; stacke = &stackb[MAX_NUM_FAILURE_ITEMS * NFAILURES]; #ifdef DEBUG_REGEX fprintf(stderr, "Entering re_match(%s)\n", string_arg); #endif /* Initialize subexpression text positions to -1 to mark ones that no ( or ( and ) or ) has been seen for. Also set all registers to inactive and mark them as not having matched anything or ever failed. */ for (mcnt = 0; mcnt < num_regs; mcnt++) { regstart[mcnt] = regend[mcnt] = old_regstart[mcnt] = old_regend[mcnt] = best_regstart[mcnt] = best_regend[mcnt] = REG_UNSET_VALUE; #ifdef __CHECKER__ reg_info[mcnt].word = 0; #endif IS_ACTIVE (reg_info[mcnt]) = 0; MATCHED_SOMETHING (reg_info[mcnt]) = 0; } /* Set up pointers to ends of strings. Don't allow the second string to be empty unless both are empty. */ /* `p' scans through the pattern as `d' scans through the data. `dend' is the end of the input string that `d' points within. `d' is advanced into the following input string whenever necessary, but this happens before fetching; therefore, at the beginning of the loop, `d' can be pointing at the end of a string, but it cannot equal string2. */ d = string + pos, dend = string + size; /* This loops over pattern commands. It exits by returning from the function if match is complete, or it drops through if match fails at this starting point in the input data. */ for (;;) { #ifdef DEBUG_REGEX fprintf(stderr, "regex loop(%d): matching 0x%02d\n", p - (unsigned char*)bufp->buffer, *p); #endif /* End of pattern means we might have succeeded. */ if (p == pend) { /* If not end of string, try backtracking. Otherwise done. */ if ((bufp->options & RE_OPTION_LONGEST) && d != dend) { if (best_regs_set) /* non-greedy, no need to backtrack */ goto restore_best_regs; while (stackp != stackb && stackp[-1] == NON_GREEDY) { if (best_regs_set) /* non-greedy, no need to backtrack */ goto restore_best_regs; POP_FAILURE_POINT(); } if (stackp != stackb) { /* More failure points to try. */ /* If exceeds best match so far, save it. */ if (! best_regs_set || (d > best_regend[0])) { best_regs_set = 1; best_regend[0] = d; /* Never use regstart[0]. */ for (mcnt = 1; mcnt < num_regs; mcnt++) { best_regstart[mcnt] = regstart[mcnt]; best_regend[mcnt] = regend[mcnt]; } } goto fail; } /* If no failure points, don't restore garbage. */ else if (best_regs_set) { restore_best_regs: /* Restore best match. */ d = best_regend[0]; for (mcnt = 0; mcnt < num_regs; mcnt++) { regstart[mcnt] = best_regstart[mcnt]; regend[mcnt] = best_regend[mcnt]; } } } /* If caller wants register contents data back, convert it to indices. */ if (regs) { regs->beg[0] = pos; regs->end[0] = d - string; for (mcnt = 1; mcnt < num_regs; mcnt++) { if (REG_UNSET(regend[mcnt])) { regs->beg[mcnt] = -1; regs->end[mcnt] = -1; continue; } regs->beg[mcnt] = regstart[mcnt] - string; regs->end[mcnt] = regend[mcnt] - string; } } FREE_AND_RETURN(stackb, (d - pos - string)); } /* Otherwise match next pattern command. */ #ifdef SWITCH_ENUM_BUG switch ((int)((enum regexpcode)*p++)) #else switch ((enum regexpcode)*p++) #endif { /* ( [or `(', as appropriate] is represented by start_memory, ) by stop_memory. Both of those commands are followed by a register number in the next byte. The text matched within the ( and ) is recorded under that number. */ case start_memory: old_regstart[*p] = regstart[*p]; regstart[*p] = d; IS_ACTIVE(reg_info[*p]) = 1; MATCHED_SOMETHING(reg_info[*p]) = 0; p += 2; continue; case stop_memory: old_regend[*p] = regend[*p]; regend[*p] = d; IS_ACTIVE(reg_info[*p]) = 0; p += 2; continue; case start_paren: case stop_paren: break; /* \ has been turned into a `duplicate' command which is followed by the numeric value of as the register number. */ case duplicate: { int regno = *p++; /* Get which register to match against */ register unsigned char *d2, *dend2; /* Check if there's corresponding group */ if (regno >= num_regs) goto fail; /* Check if corresponding group is still open */ if (IS_ACTIVE(reg_info[regno])) goto fail; /* Where in input to try to start matching. */ d2 = regstart[regno]; if (REG_UNSET(d2)) goto fail; /* Where to stop matching; if both the place to start and the place to stop matching are in the same string, then set to the place to stop, otherwise, for now have to use the end of the first string. */ dend2 = regend[regno]; if (REG_UNSET(dend2)) goto fail; for (;;) { /* At end of register contents => success */ if (d2 == dend2) break; /* If necessary, advance to next segment in data. */ PREFETCH; /* How many characters left in this segment to match. */ mcnt = dend - d; /* Want how many consecutive characters we can match in one shot, so, if necessary, adjust the count. */ if (mcnt > dend2 - d2) mcnt = dend2 - d2; /* Compare that many; failure if mismatch, else move past them. */ if ((options & RE_OPTION_IGNORECASE) ? memcmp_translate(d, d2, mcnt) : memcmp((char*)d, (char*)d2, mcnt)) goto fail; d += mcnt, d2 += mcnt; } } break; case start_nowidth: PUSH_FAILURE_POINT(0, d); if (stackp - stackb > RE_DUP_MAX) { FREE_AND_RETURN(stackb,(-2)); } EXTRACT_NUMBER_AND_INCR(mcnt, p); STORE_NUMBER(p+mcnt, stackp - stackb); continue; case stop_nowidth: EXTRACT_NUMBER_AND_INCR(mcnt, p); stackp = stackb + mcnt; d = stackp[-3]; POP_FAILURE_POINT(); continue; case stop_backtrack: EXTRACT_NUMBER_AND_INCR(mcnt, p); stackp = stackb + mcnt; POP_FAILURE_POINT(); continue; case pop_and_fail: EXTRACT_NUMBER(mcnt, p+1); stackp = stackb + mcnt; POP_FAILURE_POINT(); goto fail; case anychar: PREFETCH; if (ismbchar(*d)) { if (d + mbclen(*d) > dend) goto fail; SET_REGS_MATCHED; d += mbclen(*d); break; } if (!(options&RE_OPTION_MULTILINE) && (TRANSLATE_P() ? translate[*d] : *d) == '\n') goto fail; SET_REGS_MATCHED; d++; break; case anychar_repeat: for (;;) { PUSH_FAILURE_POINT(p, d); PREFETCH; if (ismbchar(*d)) { if (d + mbclen(*d) > dend) goto fail; SET_REGS_MATCHED; d += mbclen(*d); continue; } if (!(options&RE_OPTION_MULTILINE) && (TRANSLATE_P() ? translate[*d] : *d) == '\n') goto fail; SET_REGS_MATCHED; d++; } break; case charset: case charset_not: { int not; /* Nonzero for charset_not. */ int part = 0; /* true if matched part of mbc */ unsigned char *dsave = d + 1; int cc, c; PREFETCH; c = (unsigned char)*d++; if (ismbchar(c)) { if (d + mbclen(c) - 1 <= dend) { cc = c; MBC2WC(c, d); not = is_in_list_mbc(c, p); if (!not) { part = not = is_in_list_sbc(cc, p); } } else { not = is_in_list(c, p); } } else { if (TRANSLATE_P()) c = (unsigned char)translate[c]; not = is_in_list(c, p); } if (*(p - 1) == (unsigned char)charset_not) { not = !not; } if (!not) goto fail; p += 1 + *p + 2 + EXTRACT_UNSIGNED(&p[1 + *p])*8; SET_REGS_MATCHED; if (part) d = dsave; break; } case begline: if (size == 0 || AT_STRINGS_BEG(d)) break; if (d[-1] == '\n' && !AT_STRINGS_END(d)) break; goto fail; case endline: if (AT_STRINGS_END(d)) { break; } else if (*d == '\n') break; goto fail; /* Match at the very beginning of the string. */ case begbuf: if (AT_STRINGS_BEG(d)) break; goto fail; /* Match at the very end of the data. */ case endbuf: if (AT_STRINGS_END(d)) break; goto fail; /* Match at the very end of the data. */ case endbuf2: if (AT_STRINGS_END(d)) { break; } /* .. or newline just before the end of the data. */ if (*d == '\n' && AT_STRINGS_END(d+1)) break; goto fail; /* `or' constructs are handled by starting each alternative with an on_failure_jump that points to the start of the next alternative. Each alternative except the last ends with a jump to the joining point. (Actually, each jump except for the last one really jumps to the following jump, because tensioning the jumps is a hassle.) */ /* The start of a stupid repeat has an on_failure_jump that points past the end of the repeat text. This makes a failure point so that on failure to match a repetition, matching restarts past as many repetitions have been found with no way to fail and look for another one. */ /* A smart repeat is similar but loops back to the on_failure_jump so that each repetition makes another failure point. */ /* Match at the starting position. */ case begpos: if (d - string == beg) break; goto fail; case on_failure_jump: on_failure: EXTRACT_NUMBER_AND_INCR(mcnt, p); PUSH_FAILURE_POINT(p + mcnt, d); continue; /* The end of a smart repeat has a maybe_finalize_jump back. Change it either to a finalize_jump or an ordinary jump. */ case maybe_finalize_jump: EXTRACT_NUMBER_AND_INCR(mcnt, p); p1 = p; /* Compare the beginning of the repeat with what in the pattern follows its end. If we can establish that there is nothing that they would both match, i.e., that we would have to backtrack because of (as in, e.g., `a*a') then we can change to finalize_jump, because we'll never have to backtrack. This is not true in the case of alternatives: in `(a|ab)*' we do need to backtrack to the `ab' alternative (e.g., if the string was `ab'). But instead of trying to detect that here, the alternative has put on a dummy failure point which is what we will end up popping. */ /* Skip over open/close-group commands. */ while (p1 + 2 < pend) { if ((enum regexpcode)*p1 == stop_memory || (enum regexpcode)*p1 == start_memory) p1 += 3; /* Skip over args, too. */ else if (/*(enum regexpcode)*p1 == start_paren ||*/ (enum regexpcode)*p1 == stop_paren) p1 += 1; else break; } if (p1 == pend) p[-3] = (unsigned char)finalize_jump; else if (*p1 == (unsigned char)exactn || *p1 == (unsigned char)endline) { register int c = *p1 == (unsigned char)endline ? '\n' : p1[2]; register unsigned char *p2 = p + mcnt; /* p2[0] ... p2[2] are an on_failure_jump. Examine what follows that. */ if (p2[3] == (unsigned char)exactn && p2[5] != c) p[-3] = (unsigned char)finalize_jump; else if (p2[3] == (unsigned char)charset || p2[3] == (unsigned char)charset_not) { int not; if (ismbchar(c)) { unsigned char *pp = p1+3; MBC2WC(c, pp); } /* `is_in_list()' is TRUE if c would match */ /* That means it is not safe to finalize. */ not = is_in_list(c, p2 + 4); if (p2[3] == (unsigned char)charset_not) not = !not; if (!not) p[-3] = (unsigned char)finalize_jump; } } p -= 2; /* Point at relative address again. */ if (p[-1] != (unsigned char)finalize_jump) { p[-1] = (unsigned char)jump; goto nofinalize; } /* Note fall through. */ /* The end of a stupid repeat has a finalize_jump back to the start, where another failure point will be made which will point to after all the repetitions found so far. */ /* Take off failure points put on by matching on_failure_jump because didn't fail. Also remove the register information put on by the on_failure_jump. */ case finalize_jump: if (stackp > stackb && stackp[-3] == d) { p = stackp[-4]; POP_FAILURE_POINT(); continue; } POP_FAILURE_POINT(); /* Note fall through. */ /* We need this opcode so we can detect where alternatives end in `group_match_null_string_p' et al. */ case jump_past_alt: /* fall through */ /* Jump without taking off any failure points. */ case jump: nofinalize: EXTRACT_NUMBER_AND_INCR(mcnt, p); if (mcnt < 0 && stackp > stackb && stackp[-3] == d) /* avoid infinite loop */ goto fail; p += mcnt; continue; case dummy_failure_jump: /* Normally, the on_failure_jump pushes a failure point, which then gets popped at finalize_jump. We will end up at finalize_jump, also, and with a pattern of, say, `a+', we are skipping over the on_failure_jump, so we have to push something meaningless for finalize_jump to pop. */ PUSH_FAILURE_POINT(0, 0); goto nofinalize; /* At the end of an alternative, we need to push a dummy failure point in case we are followed by a `finalize_jump', because we don't want the failure point for the alternative to be popped. For example, matching `(a|ab)*' against `aab' requires that we match the `ab' alternative. */ case push_dummy_failure: /* See comments just above at `dummy_failure_jump' about the two zeroes. */ p1 = p; /* Skip over open/close-group commands. */ while (p1 + 2 < pend) { if ((enum regexpcode)*p1 == stop_memory || (enum regexpcode)*p1 == start_memory) p1 += 3; /* Skip over args, too. */ else if (/*(enum regexpcode)*p1 == start_paren ||*/ (enum regexpcode)*p1 == stop_paren) p1 += 1; else break; } if (p1 < pend && (enum regexpcode)*p1 == jump) p[-1] = unused; else PUSH_FAILURE_POINT(0, 0); break; /* Have to succeed matching what follows at least n times. Then just handle like an on_failure_jump. */ case succeed_n: EXTRACT_NUMBER(mcnt, p + 2); /* Originally, this is how many times we HAVE to succeed. */ if (mcnt != 0) { mcnt--; p += 2; PUSH_FAILURE_COUNT(p); STORE_NUMBER_AND_INCR(p, mcnt); PUSH_FAILURE_POINT(0, 0); } else { goto on_failure; } continue; case jump_n: EXTRACT_NUMBER(mcnt, p + 2); /* Originally, this is how many times we CAN jump. */ if (mcnt) { mcnt--; PUSH_FAILURE_COUNT(p + 2); STORE_NUMBER(p + 2, mcnt); goto nofinalize; /* Do the jump without taking off any failure points. */ } /* If don't have to jump any more, skip over the rest of command. */ else p += 4; continue; case set_number_at: EXTRACT_NUMBER_AND_INCR(mcnt, p); p1 = p + mcnt; EXTRACT_NUMBER_AND_INCR(mcnt, p); STORE_NUMBER(p1, mcnt); continue; case try_next: EXTRACT_NUMBER_AND_INCR(mcnt, p); if (p + mcnt < pend) { PUSH_FAILURE_POINT(p, d); stackp[-1] = NON_GREEDY; } p += mcnt; continue; case finalize_push: POP_FAILURE_POINT(); EXTRACT_NUMBER_AND_INCR(mcnt, p); if (mcnt < 0 && stackp > stackb && stackp[-3] == d) /* avoid infinite loop */ goto fail; PUSH_FAILURE_POINT(p + mcnt, d); stackp[-1] = NON_GREEDY; continue; case finalize_push_n: EXTRACT_NUMBER(mcnt, p + 2); /* Originally, this is how many times we CAN jump. */ if (mcnt) { int pos, i; mcnt--; STORE_NUMBER(p + 2, mcnt); EXTRACT_NUMBER(pos, p); EXTRACT_NUMBER(i, p+pos+5); if (i > 0) goto nofinalize; POP_FAILURE_POINT(); EXTRACT_NUMBER_AND_INCR(mcnt, p); PUSH_FAILURE_POINT(p + mcnt, d); stackp[-1] = NON_GREEDY; p += 2; /* skip n */ } /* If don't have to push any more, skip over the rest of command. */ else p += 4; continue; /* Ignore these. Used to ignore the n of succeed_n's which currently have n == 0. */ case unused: continue; case casefold_on: options |= RE_OPTION_IGNORECASE; continue; case casefold_off: options &= ~RE_OPTION_IGNORECASE; continue; case option_set: options = *p++; continue; case wordbound: if (AT_STRINGS_BEG(d)) { if (AT_STRINGS_END(d)) goto fail; if (IS_A_LETTER(d)) break; else goto fail; } if (AT_STRINGS_END(d)) { if (PREV_IS_A_LETTER(d)) break; else goto fail; } if (PREV_IS_A_LETTER(d) != IS_A_LETTER(d)) break; goto fail; case notwordbound: if (AT_STRINGS_BEG(d)) { if (IS_A_LETTER(d)) goto fail; else break; } if (AT_STRINGS_END(d)) { if (PREV_IS_A_LETTER(d)) goto fail; else break; } if (PREV_IS_A_LETTER(d) != IS_A_LETTER(d)) goto fail; break; case wordbeg: if (IS_A_LETTER(d) && (AT_STRINGS_BEG(d) || !PREV_IS_A_LETTER(d))) break; goto fail; case wordend: if (!AT_STRINGS_BEG(d) && PREV_IS_A_LETTER(d) && (!IS_A_LETTER(d) || AT_STRINGS_END(d))) break; goto fail; case wordchar: PREFETCH; if (!IS_A_LETTER(d)) goto fail; if (ismbchar(*d) && d + mbclen(*d) - 1 < dend) d += mbclen(*d) - 1; d++; SET_REGS_MATCHED; break; case notwordchar: PREFETCH; if (IS_A_LETTER(d)) goto fail; if (ismbchar(*d) && d + mbclen(*d) - 1 < dend) d += mbclen(*d) - 1; d++; SET_REGS_MATCHED; break; case exactn: /* Match the next few pattern characters exactly. mcnt is how many characters to match. */ mcnt = *p++; /* This is written out as an if-else so we don't waste time testing `translate' inside the loop. */ if (TRANSLATE_P()) { do { unsigned char c; PREFETCH; if (*p == 0xff) { p++; if (!--mcnt || AT_STRINGS_END(d) || (unsigned char)*d++ != (unsigned char)*p++) goto fail; continue; } c = *d++; if (ismbchar(c)) { int n; if (c != (unsigned char)*p++) goto fail; for (n = mbclen(c) - 1; n > 0; n--) if (!--mcnt /* redundant check if pattern was compiled properly. */ || AT_STRINGS_END(d) || (unsigned char)*d++ != (unsigned char)*p++) goto fail; continue; } /* compiled code translation needed for ruby */ if ((unsigned char)translate[c] != (unsigned char)translate[*p++]) goto fail; } while (--mcnt); } else { do { PREFETCH; if (*p == 0xff) {p++; mcnt--;} if (*d++ != *p++) goto fail; } while (--mcnt); } SET_REGS_MATCHED; break; } #ifdef RUBY CHECK_INTS; #endif continue; /* Successfully executed one pattern command; keep going. */ /* Jump here if any matching operation fails. */ fail: if (stackp != stackb) { /* A restart point is known. Restart there and pop it. */ short last_used_reg, this_reg; /* If this failure point is from a dummy_failure_point, just skip it. */ if (stackp[-4] == 0 || (best_regs_set && stackp[-1] == NON_GREEDY)) { POP_FAILURE_POINT(); goto fail; } stackp--; /* discard greedy flag */ options = (long)*--stackp; d = *--stackp; p = *--stackp; /* Restore register info. */ last_used_reg = (long)*--stackp; /* Make the ones that weren't saved -1 or 0 again. */ for (this_reg = num_regs - 1; this_reg > last_used_reg; this_reg--) { regend[this_reg] = REG_UNSET_VALUE; regstart[this_reg] = REG_UNSET_VALUE; IS_ACTIVE(reg_info[this_reg]) = 0; MATCHED_SOMETHING(reg_info[this_reg]) = 0; } /* And restore the rest from the stack. */ for ( ; this_reg > 0; this_reg--) { reg_info[this_reg].word = *--stackp; regend[this_reg] = *--stackp; regstart[this_reg] = *--stackp; } mcnt = (long)*--stackp; while (mcnt--) { POP_FAILURE_COUNT(); } if (p < pend) { int is_a_jump_n = 0; int failed_paren = 0; p1 = p; /* If failed to a backwards jump that's part of a repetition loop, need to pop this failure point and use the next one. */ switch ((enum regexpcode)*p1) { case jump_n: case finalize_push_n: is_a_jump_n = 1; case maybe_finalize_jump: case finalize_jump: case finalize_push: case jump: p1++; EXTRACT_NUMBER_AND_INCR(mcnt, p1); if (mcnt >= 0) break; /* should be backward jump */ p1 += mcnt; if (( is_a_jump_n && (enum regexpcode)*p1 == succeed_n) || (!is_a_jump_n && (enum regexpcode)*p1 == on_failure_jump)) { if (failed_paren) { p1++; EXTRACT_NUMBER_AND_INCR(mcnt, p1); PUSH_FAILURE_POINT(p1 + mcnt, d); } goto fail; } break; default: /* do nothing */; } } } else break; /* Matching at this starting point really fails. */ } if (best_regs_set) goto restore_best_regs; FREE_AND_RETURN(stackb,(-1)); /* Failure to match. */ memory_exhausted: FREE_AND_RETURN(stackb,(-2)); } static int memcmp_translate(s1, s2, len) unsigned char *s1, *s2; register int len; { register unsigned char *p1 = s1, *p2 = s2, c; while (len) { c = *p1++; if (ismbchar(c)) { int n; if (c != *p2++) return 1; for (n = mbclen(c) - 1; n > 0; n--) if (!--len || *p1++ != *p2++) return 1; } else if (translate[c] != translate[*p2++]) return 1; len--; } return 0; } void re_copy_registers(regs1, regs2) struct re_registers *regs1, *regs2; { int i; if (regs1 == regs2) return; if (regs1->allocated == 0) { regs1->beg = TMALLOC(regs2->num_regs, int); regs1->end = TMALLOC(regs2->num_regs, int); regs1->allocated = regs2->num_regs; } else if (regs1->allocated < regs2->num_regs) { TREALLOC(regs1->beg, regs2->num_regs, int); TREALLOC(regs1->end, regs2->num_regs, int); regs1->allocated = regs2->num_regs; } for (i=0; inum_regs; i++) { regs1->beg[i] = regs2->beg[i]; regs1->end[i] = regs2->end[i]; } regs1->num_regs = regs2->num_regs; } void re_free_registers(regs) struct re_registers *regs; { if (regs->allocated == 0) return; if (regs->beg) xfree(regs->beg); if (regs->end) xfree(regs->end); } /* Functions for multi-byte support. Created for grep multi-byte extension Jul., 1993 by t^2 (Takahiro Tanimoto) Last change: Jul. 9, 1993 by t^2 */ static const unsigned char mbctab_ascii[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; static const unsigned char mbctab_euc[] = { /* 0xA1-0xFE */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, }; static const unsigned char mbctab_sjis[] = { /* 0x81-0x9F,0xE0-0xFC */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 }; static const unsigned char mbctab_sjis_trail[] = { /* 0x40-0x7E,0x80-0xFC */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 }; static const unsigned char mbctab_utf8[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 0, 0, }; const unsigned char *re_mbctab = mbctab_ascii; void re_mbcinit(mbctype) int mbctype; { switch (mbctype) { case MBCTYPE_ASCII: re_mbctab = mbctab_ascii; current_mbctype = MBCTYPE_ASCII; break; case MBCTYPE_EUC: re_mbctab = mbctab_euc; current_mbctype = MBCTYPE_EUC; break; case MBCTYPE_SJIS: re_mbctab = mbctab_sjis; current_mbctype = MBCTYPE_SJIS; break; case MBCTYPE_UTF8: re_mbctab = mbctab_utf8; current_mbctype = MBCTYPE_UTF8; break; } } #define mbc_isfirst(t, c) (t)[(unsigned char)(c)] #define mbc_len(t, c) ((t)[(unsigned char)(c)]+1) static unsigned int asc_startpos(string, pos) const char *string; unsigned int pos; { return pos; } #define euc_islead(c) ((unsigned char)((c) - 0xa1) > 0xfe - 0xa1) #define euc_mbclen(c) mbc_len(mbctab_euc, (c)) static unsigned int euc_startpos(string, pos) const char *string; unsigned int pos; { unsigned int i = pos, w; while (i > 0 && !euc_islead(string[i])) { --i; } if (i == pos || i + (w = euc_mbclen(string[i])) > pos) { return i; } i += w; return i + ((pos - i) & ~1); } #define sjis_isfirst(c) mbc_isfirst(mbctab_sjis, (c)) #define sjis_istrail(c) mbctab_sjis_trail[(unsigned char)(c)] #define sjis_mbclen(c) mbc_len(mbctab_sjis, (c)) static unsigned int sjis_startpos(string, pos) const char *string; unsigned int pos; { unsigned int i = pos, w; if (i > 0 && sjis_istrail(string[i])) { do { if (!sjis_isfirst(string[--i])) { ++i; break; } } while (i > 0); } if (i == pos || i + (w = sjis_mbclen(string[i])) > pos) { return i; } i += w; return i + ((pos - i) & ~1); } #define utf8_islead(c) ((unsigned char)((c) & 0xc0) != 0x80) #define utf8_mbclen(c) mbc_len(mbctab_utf8, (c)) static unsigned int utf8_startpos(string, pos) const char *string; unsigned int pos; { unsigned int i = pos, w; while (i > 0 && !utf8_islead(string[i])) { --i; } if (i == pos || i + (w = utf8_mbclen(string[i])) > pos) { return i; } return i + w; } /* vi: sw=2 ts=8 Local variables: mode : C c-file-style : "gnu" tab-width : 8 End */ /********************************************************************** ruby.c - $Author: shyouhei $ $Date: 2008-07-10 11:36:08 +0200 (Thu, 10 Jul 2008) $ created at: Tue Aug 10 12:47:31 JST 1993 Copyright (C) 1993-2003 Yukihiro Matsumoto Copyright (C) 2000 Network Applied Communication Laboratory, Inc. Copyright (C) 2000 Information-technology Promotion Agency, Japan **********************************************************************/ #if defined _WIN32 || defined __CYGWIN__ #include #endif #if defined __CYGWIN__ #include #endif #ifdef _WIN32_WCE #include #include "wince.h" #endif #include "ruby.h" #include "dln.h" #include "node.h" #include #include #include #ifdef __hpux #include #endif #ifdef HAVE_UNISTD_H #include #endif #ifndef HAVE_STRING_H char *strchr _((const char*,const char)); char *strrchr _((const char*,const char)); char *strstr _((const char*,const char*)); #endif #include "util.h" #ifndef HAVE_STDLIB_H char *getenv(); #endif VALUE ruby_debug = Qfalse; VALUE ruby_verbose = Qfalse; static int sflag = 0; static int xflag = 0; extern int ruby_yydebug; char *ruby_inplace_mode = Qfalse; static void load_stdin _((void)); static void load_file _((const char *, int)); static void forbid_setid _((const char *)); static VALUE do_loop = Qfalse, do_print = Qfalse; static VALUE do_check = Qfalse, do_line = Qfalse; static VALUE do_split = Qfalse; static char *script; static int origargc; static char **origargv; static void usage(name) const char *name; { /* This message really ought to be max 23 lines. * Removed -h because the user already knows that option. Others? */ static char *usage_msg[] = { "-0[octal] specify record separator (\\0, if no argument)", "-a autosplit mode with -n or -p (splits $_ into $F)", "-c check syntax only", "-Cdirectory cd to directory, before executing your script", "-d set debugging flags (set $DEBUG to true)", "-e 'command' one line of script. Several -e's allowed. Omit [programfile]", "-Fpattern split() pattern for autosplit (-a)", "-i[extension] edit ARGV files in place (make backup if extension supplied)", "-Idirectory specify $LOAD_PATH directory (may be used more than once)", "-Kkcode specifies KANJI (Japanese) code-set", "-l enable line ending processing", "-n assume 'while gets(); ... end' loop around your script", "-p assume loop like -n but print line also like sed", "-rlibrary require the library, before executing your script", "-s enable some switch parsing for switches after script name", "-S look for the script using PATH environment variable", "-T[level] turn on tainting checks", "-v print version number, then turn on verbose mode", "-w turn warnings on for your script", "-W[level] set warning level; 0=silence, 1=medium, 2=verbose (default)", "-x[directory] strip off text before #!ruby line and perhaps cd to directory", "--copyright print the copyright", "--version print the version", NULL }; char **p = usage_msg; printf("Usage: %s [switches] [--] [programfile] [arguments]\n", name); while (*p) printf(" %s\n", *p++); } extern VALUE rb_load_path; #ifndef CharNext /* defined as CharNext[AW] on Windows. */ #define CharNext(p) ((p) + mblen(p, RUBY_MBCHAR_MAXSIZE)) #endif #if defined DOSISH || defined __CYGWIN__ static inline void translate_char(char *p, int from, int to) { while (*p) { if ((unsigned char)*p == from) *p = to; p = CharNext(p); } } #endif #if defined _WIN32 || defined __CYGWIN__ || defined __DJGPP__ static VALUE rubylib_mangled_path(const char *s, unsigned int l) { static char *newp, *oldp; static int newl, oldl, notfound; char *ptr; VALUE ret; if (!newp && !notfound) { newp = getenv("RUBYLIB_PREFIX"); if (newp) { oldp = newp = strdup(newp); while (*newp && !ISSPACE(*newp) && *newp != ';') { newp = CharNext(newp); /* Skip digits. */ } oldl = newp - oldp; while (*newp && (ISSPACE(*newp) || *newp == ';')) { newp = CharNext(newp); /* Skip whitespace. */ } newl = strlen(newp); if (newl == 0 || oldl == 0) { rb_fatal("malformed RUBYLIB_PREFIX"); } translate_char(newp, '\\', '/'); } else { notfound = 1; } } if (!newp || l < oldl || strncasecmp(oldp, s, oldl) != 0) { return rb_str_new(s, l); } ret = rb_str_new(0, l + newl - oldl); ptr = RSTRING_PTR(ret); memcpy(ptr, newp, newl); memcpy(ptr + newl, s + oldl, l - oldl); ptr[l + newl - oldl] = 0; return ret; } static VALUE rubylib_mangled_path2(const char *s) { return rubylib_mangled_path(s, strlen(s)); } #else #define rubylib_mangled_path rb_str_new #define rubylib_mangled_path2 rb_str_new2 #endif static void push_include _((const char *path)); static void push_include(path) const char *path; { const char sep = PATH_SEP_CHAR; const char *p, *s; p = path; while (*p) { while (*p == sep) p++; if (!*p) break; for (s = p; *s && *s != sep; s = CharNext(s)); rb_ary_push(rb_load_path, rubylib_mangled_path(p, s - p)); p = s; } } #ifdef __CYGWIN__ static void push_include_cygwin(const char *path) { const char *p, *s; char rubylib[FILENAME_MAX]; VALUE buf = 0; p = path; while (*p) { unsigned int len; while (*p == ';') p++; if (!*p) break; for (s = p; *s && *s != ';'; s = CharNext(s)); len = s - p; if (*s) { if (!buf) { buf = rb_str_new(p, len); p = RSTRING_PTR(buf); } else { rb_str_resize(buf, len); p = strncpy(RSTRING_PTR(buf), p, len); } } if (cygwin_conv_to_posix_path(p, rubylib) == 0) p = rubylib; push_include(p); if (!*s) break; p = s + 1; } } #define push_include push_include_cygwin #endif void ruby_incpush(path) const char *path; { if (path == 0) return; push_include(path); } #if defined DOSISH || defined __CYGWIN__ #define LOAD_RELATIVE 1 #endif void ruby_init_loadpath() { #if defined LOAD_RELATIVE char libpath[FILENAME_MAX+1]; char *p; int rest; #if defined _WIN32 || defined __CYGWIN__ HMODULE libruby = NULL; MEMORY_BASIC_INFORMATION m; #ifndef _WIN32_WCE memset(&m, 0, sizeof(m)); if (VirtualQuery(ruby_init_loadpath, &m, sizeof(m)) && m.State == MEM_COMMIT) libruby = (HMODULE)m.AllocationBase; #endif GetModuleFileName(libruby, libpath, sizeof libpath); #elif defined(DJGPP) extern char *__dos_argv0; strncpy(libpath, __dos_argv0, FILENAME_MAX); #elif defined(__human68k__) extern char **_argv; strncpy(libpath, _argv[0], FILENAME_MAX); #elif defined(__EMX__) _execname(libpath, FILENAME_MAX); #endif libpath[FILENAME_MAX] = '\0'; #if defined DOSISH translate_char(libpath, '\\', '/'); #elif defined __CYGWIN__ { char rubylib[FILENAME_MAX]; cygwin_conv_to_posix_path(libpath, rubylib); strncpy(libpath, rubylib, sizeof(libpath)); } #endif p = strrchr(libpath, '/'); if (p) { *p = 0; if (p - libpath > 3 && !strcasecmp(p - 4, "/bin")) { p -= 4; *p = 0; } } else { strcpy(libpath, "."); p = libpath + 1; } rest = FILENAME_MAX - (p - libpath); #define RUBY_RELATIVE(path) (strncpy(p, (path), rest), libpath) #else #define RUBY_RELATIVE(path) (path) #endif #define incpush(path) rb_ary_push(rb_load_path, rubylib_mangled_path2(path)) if (rb_safe_level() == 0) { ruby_incpush(getenv("RUBYLIB")); } #ifdef RUBY_SEARCH_PATH incpush(RUBY_RELATIVE(RUBY_SEARCH_PATH)); #endif incpush(RUBY_RELATIVE(RUBY_SITE_LIB2)); #ifdef RUBY_SITE_THIN_ARCHLIB incpush(RUBY_RELATIVE(RUBY_SITE_THIN_ARCHLIB)); #endif incpush(RUBY_RELATIVE(RUBY_SITE_ARCHLIB)); incpush(RUBY_RELATIVE(RUBY_SITE_LIB)); incpush(RUBY_RELATIVE(RUBY_LIB)); #ifdef RUBY_THIN_ARCHLIB incpush(RUBY_RELATIVE(RUBY_THIN_ARCHLIB)); #endif incpush(RUBY_RELATIVE(RUBY_ARCHLIB)); if (rb_safe_level() == 0) { incpush("."); } } struct req_list { char *name; struct req_list *next; }; static struct req_list req_list_head, *req_list_last = &req_list_head; static void add_modules(mod) const char *mod; { struct req_list *list; list = ALLOC(struct req_list); list->name = ALLOC_N(char, strlen(mod)+1); strcpy(list->name, mod); list->next = 0; req_list_last->next = list; req_list_last = list; } extern void Init_ext _((void)); static void require_libraries() { extern NODE *ruby_eval_tree; extern NODE *ruby_eval_tree_begin; NODE *save[3]; struct req_list *list = req_list_head.next; struct req_list *tmp; save[0] = ruby_eval_tree; save[1] = ruby_eval_tree_begin; save[2] = NEW_NEWLINE(0); ruby_eval_tree = ruby_eval_tree_begin = 0; ruby_current_node = 0; Init_ext(); /* should be called here for some reason :-( */ ruby_current_node = save[2]; ruby_set_current_source(); req_list_last = 0; while (list) { int state; ruby_current_node = 0; rb_protect((VALUE (*)(VALUE))rb_require, (VALUE)list->name, &state); if (state) rb_jump_tag(state); tmp = list->next; free(list->name); free(list); list = tmp; ruby_current_node = save[2]; ruby_set_current_source(); } req_list_head.next = 0; ruby_eval_tree = save[0]; ruby_eval_tree_begin = save[1]; rb_gc_force_recycle((VALUE)save[2]); ruby_current_node = 0; } static void process_sflag() { if (sflag) { long n; VALUE *args; n = RARRAY(rb_argv)->len; args = RARRAY(rb_argv)->ptr; while (n > 0) { VALUE v = *args++; char *s = StringValuePtr(v); char *p; int hyphen = Qfalse; if (s[0] != '-') break; n--; if (s[1] == '-' && s[2] == '\0') break; v = Qtrue; /* check if valid name before replacing - with _ */ for (p = s + 1; *p; p++) { if (*p == '=') { *p++ = '\0'; v = rb_str_new2(p); break; } if (*p == '-') { hyphen = Qtrue; } else if (*p != '_' && !ISALNUM(*p)) { VALUE name_error[2]; name_error[0] = rb_str_new2("invalid name for global variable - "); if (!(p = strchr(p, '='))) { rb_str_cat2(name_error[0], s); } else { rb_str_cat(name_error[0], s, p - s); } name_error[1] = args[-1]; rb_exc_raise(rb_class_new_instance(2, name_error, rb_eNameError)); } } s[0] = '$'; if (hyphen) { for (p = s + 1; *p; ++p) { if (*p == '-') *p = '_'; } } rb_gv_set(s, v); } n = RARRAY(rb_argv)->len - n; while (n--) { rb_ary_shift(rb_argv); } } sflag = 0; } static void proc_options _((int argc, char **argv)); static char* moreswitches(s) char *s; { int argc; char *argv[3]; char *p = s; argc = 2; argv[0] = argv[2] = 0; while (*s && !ISSPACE(*s)) s++; argv[1] = ALLOCA_N(char, s-p+2); argv[1][0] = '-'; strncpy(argv[1]+1, p, s-p); argv[1][s-p+1] = '\0'; proc_options(argc, argv); while (*s && ISSPACE(*s)) s++; return s; } static void proc_options(argc, argv) int argc; char **argv; { char *argv0 = argv[0]; int do_search; char *s; NODE *volatile script_node = 0; int version = 0; int copyright = 0; int verbose = 0; VALUE e_script = Qfalse; if (argc == 0) return; do_search = Qfalse; for (argc--,argv++; argc > 0; argc--,argv++) { if (argv[0][0] != '-' || !argv[0][1]) break; s = argv[0]+1; reswitch: switch (*s) { case 'a': do_split = Qtrue; s++; goto reswitch; case 'p': do_print = Qtrue; /* through */ case 'n': do_loop = Qtrue; s++; goto reswitch; case 'd': ruby_debug = Qtrue; ruby_verbose = Qtrue; s++; goto reswitch; case 'y': ruby_yydebug = 1; s++; goto reswitch; case 'v': if (argv0 == 0 || verbose) { s++; goto reswitch; } ruby_show_version(); verbose = 1; case 'w': ruby_verbose = Qtrue; s++; goto reswitch; case 'W': { int numlen; int v = 2; /* -W as -W2 */ if (*++s) { v = scan_oct(s, 1, &numlen); if (numlen == 0) v = 1; s += numlen; } switch (v) { case 0: ruby_verbose = Qnil; break; case 1: ruby_verbose = Qfalse; break; default: ruby_verbose = Qtrue; break; } } goto reswitch; case 'c': do_check = Qtrue; s++; goto reswitch; case 's': forbid_setid("-s"); sflag = 1; s++; goto reswitch; case 'h': usage(origargv[0]); exit(0); case 'l': do_line = Qtrue; rb_output_rs = rb_rs; s++; goto reswitch; case 'S': forbid_setid("-S"); do_search = Qtrue; s++; goto reswitch; case 'e': forbid_setid("-e"); if (!*++s) { s = argv[1]; argc--,argv++; } if (!s) { fprintf(stderr, "%s: no code specified for -e\n", origargv[0]); exit(2); } if (!e_script) { e_script = rb_str_new(0,0); if (script == 0) script = "-e"; } rb_str_cat2(e_script, s); rb_str_cat2(e_script, "\n"); break; case 'r': forbid_setid("-r"); if (*++s) { add_modules(s); } else if (argv[1]) { add_modules(argv[1]); argc--,argv++; } break; case 'i': forbid_setid("-i"); if (ruby_inplace_mode) free(ruby_inplace_mode); ruby_inplace_mode = strdup(s+1); break; case 'x': xflag = Qtrue; s++; if (*s && chdir(s) < 0) { rb_fatal("Can't chdir to %s", s); } break; case 'C': case 'X': s++; if (!*s) { s = argv[1]; argc--,argv++; } if (!s || !*s) { rb_fatal("Can't chdir"); } if (chdir(s) < 0) { rb_fatal("Can't chdir to %s", s); } break; case 'F': if (*++s) { rb_fs = rb_reg_new(s, strlen(s), 0); } break; case 'K': if (*++s) { rb_set_kcode(s); s++; } goto reswitch; case 'T': { int numlen; int v = 1; if (*++s) { v = scan_oct(s, 2, &numlen); if (numlen == 0) v = 1; s += numlen; } rb_set_safe_level(v); } goto reswitch; case 'I': forbid_setid("-I"); if (*++s) ruby_incpush(s); else if (argv[1]) { ruby_incpush(argv[1]); argc--,argv++; } break; case '0': { int numlen; int v; char c; v = scan_oct(s, 4, &numlen); s += numlen; if (v > 0377) rb_rs = Qnil; else if (v == 0 && numlen >= 2) { rb_rs = rb_str_new2("\n\n"); } else { c = v & 0xff; rb_rs = rb_str_new(&c, 1); } } goto reswitch; case '-': if (!s[1] || (s[1] == '\r' && !s[2])) { argc--,argv++; goto switch_end; } s++; if (strcmp("copyright", s) == 0) copyright = 1; else if (strcmp("debug", s) == 0) { ruby_debug = Qtrue; ruby_verbose = Qtrue; } else if (strcmp("version", s) == 0) version = 1; else if (strcmp("verbose", s) == 0) { verbose = 1; ruby_verbose = Qtrue; } else if (strcmp("yydebug", s) == 0) ruby_yydebug = 1; else if (strcmp("help", s) == 0) { usage(origargv[0]); exit(0); } else { fprintf(stderr, "%s: invalid option --%s (-h will show valid options)\n", origargv[0], s); exit(2); } break; case '\r': if (!s[1]) break; default: { const char *format; if (ISPRINT(*s)) { format = "%s: invalid option -%c (-h will show valid options)\n"; } else { format = "%s: invalid option -\\%03o (-h will show valid options)\n"; } fprintf(stderr, format, origargv[0], (int)(unsigned char)*s); } exit(2); case 0: break; } } switch_end: if (argv0 == 0) return; if (rb_safe_level() == 0 && (s = getenv("RUBYOPT"))) { while (ISSPACE(*s)) s++; if (*s == 'T' || (*s == '-' && *(s+1) == 'T')) { int numlen; int v = 1; if (*s != 'T') ++s; if (*++s) { v = scan_oct(s, 2, &numlen); if (numlen == 0) v = 1; } rb_set_safe_level(v); } else { while (s && *s) { if (*s == '-') { s++; if (ISSPACE(*s)) { do {s++;} while (ISSPACE(*s)); continue; } } if (!*s) break; if (!strchr("IdvwWrK", *s)) rb_raise(rb_eRuntimeError, "illegal switch in RUBYOPT: -%c", *s); s = moreswitches(s); } } } if (version) { ruby_show_version(); exit(0); } if (copyright) { ruby_show_copyright(); } if (rb_safe_level() >= 4) { OBJ_TAINT(rb_argv); OBJ_TAINT(rb_load_path); } if (!e_script) { if (argc == 0) { /* no more args */ if (verbose) exit(0); script = "-"; } else { script = argv[0]; if (script[0] == '\0') { script = "-"; } else if (do_search) { char *path = getenv("RUBYPATH"); script = 0; if (path) { script = dln_find_file(argv[0], path); } if (!script) { script = dln_find_file(argv[0], getenv(PATH_ENV)); } if (!script) script = argv[0]; script = ruby_sourcefile = rb_source_filename(script); script_node = NEW_NEWLINE(0); } #if defined DOSISH || defined __CYGWIN__ translate_char(script, '\\', '/'); #endif argc--; argv++; } } ruby_script(script); ruby_set_argv(argc, argv); process_sflag(); ruby_init_loadpath(); ruby_sourcefile = rb_source_filename(argv0); if (e_script) { require_libraries(); rb_compile_string(script, e_script, 1); } else if (strlen(script) == 1 && script[0] == '-') { load_stdin(); } else { load_file(script, 1); } process_sflag(); xflag = 0; if (rb_safe_level() >= 4) { FL_UNSET(rb_argv, FL_TAINT); FL_UNSET(rb_load_path, FL_TAINT); } } extern int ruby__end__seen; static void load_file(fname, script) const char *fname; int script; { extern VALUE rb_stdin; VALUE f; int line_start = 1; if (!fname) rb_load_fail(fname); if (strcmp(fname, "-") == 0) { f = rb_stdin; } else { FILE *fp = fopen(fname, "r"); if (fp == NULL) { rb_load_fail(fname); } fclose(fp); f = rb_file_open(fname, "r"); #if defined DOSISH || defined __CYGWIN__ { char *ext = strrchr(fname, '.'); if (ext && strcasecmp(ext, ".exe") == 0) rb_io_binmode(f); } #endif } if (script) { VALUE c = 1; /* something not nil */ VALUE line; char *p; if (xflag) { forbid_setid("-x"); xflag = Qfalse; while (!NIL_P(line = rb_io_gets(f))) { line_start++; if (RSTRING(line)->len > 2 && RSTRING(line)->ptr[0] == '#' && RSTRING(line)->ptr[1] == '!') { if ((p = strstr(RSTRING(line)->ptr, "ruby")) != 0) { goto start_read; } } } rb_raise(rb_eLoadError, "no Ruby script found in input"); } c = rb_io_getc(f); if (c == INT2FIX('#')) { line = rb_io_gets(f); if (NIL_P(line)) return; line_start++; if (RSTRING(line)->len > 2 && RSTRING(line)->ptr[0] == '!') { if ((p = strstr(RSTRING(line)->ptr, "ruby")) == 0) { /* not ruby script, kick the program */ char **argv; char *path; char *pend = RSTRING(line)->ptr + RSTRING(line)->len; p = RSTRING(line)->ptr + 1; /* skip `#!' */ if (pend[-1] == '\n') pend--; /* chomp line */ if (pend[-1] == '\r') pend--; *pend = '\0'; while (p < pend && ISSPACE(*p)) p++; path = p; /* interpreter path */ while (p < pend && !ISSPACE(*p)) p++; *p++ = '\0'; if (p < pend) { argv = ALLOCA_N(char*, origargc+3); argv[1] = p; MEMCPY(argv+2, origargv+1, char*, origargc); } else { argv = origargv; } argv[0] = path; execv(path, argv); ruby_sourcefile = rb_source_filename(fname); ruby_sourceline = 1; rb_fatal("Can't exec %s", path); } start_read: p += 4; RSTRING(line)->ptr[RSTRING(line)->len-1] = '\0'; if (RSTRING(line)->ptr[RSTRING(line)->len-2] == '\r') RSTRING(line)->ptr[RSTRING(line)->len-2] = '\0'; if ((p = strstr(p, " -")) != 0) { p++; /* skip space before `-' */ while (*p == '-') { p = moreswitches(p+1); } } } } else if (!NIL_P(c)) { rb_io_ungetc(f, c); } require_libraries(); /* Why here? unnatural */ if (NIL_P(c)) return; } rb_compile_file(fname, f, line_start); if (script && ruby__end__seen) { rb_define_global_const("DATA", f); } else if (f != rb_stdin) { rb_io_close(f); } if (ruby_parser_stack_on_heap()) { rb_gc(); } } void rb_load_file(fname) const char *fname; { load_file(fname, 0); } static void load_stdin() { forbid_setid("program input from stdin"); load_file("-", 1); } VALUE rb_progname; VALUE rb_argv; VALUE rb_argv0; #if defined(PSTAT_SETCMD) || defined(HAVE_SETPROCTITLE) #elif defined(_WIN32) #elif defined(HAVE_SETENV) && defined(HAVE_UNSETENV) #else #define USE_ENVSPACE_FOR_ARG0 #endif #ifdef USE_ENVSPACE_FOR_ARG0 static struct { char *begin, *end; } envspace; extern char **environ; static void set_arg0space() { char *s; int i; if (!environ || (s = environ[0]) == NULL) return; envspace.begin = s; s += strlen(s); for (i = 1; environ[i]; i++) { if (environ[i] == s + 1) { s++; s += strlen(s); /* this one is ok too */ } } envspace.end = s; } #else #define set_arg0space() ((void)0) #endif static int get_arglen(int argc, char **argv) { char *s = argv[0]; int i; if (!argc) return 0; s += strlen(s); /* See if all the arguments are contiguous in memory */ for (i = 1; i < argc; i++) { if (argv[i] == s + 1) { s++; s += strlen(s); /* this one is ok too */ } else { break; } } #if defined(USE_ENVSPACE_FOR_ARG0) if (environ && (s == environ[0])) { s += strlen(s); for (i = 1; environ[i]; i++) { if (environ[i] == s + 1) { s++; s += strlen(s); /* this one is ok too */ } } ruby_setenv("", NULL); /* duplicate environ vars */ } #endif return s - argv[0]; } static void set_arg0(val, id) VALUE val; ID id; { VALUE progname; char *s; long i; int j; #if !defined(PSTAT_SETCMD) && !defined(HAVE_SETPROCTITLE) static int len = 0; #endif if (origargv == 0) rb_raise(rb_eRuntimeError, "$0 not initialized"); StringValue(val); s = RSTRING(val)->ptr; i = RSTRING(val)->len; #if defined(PSTAT_SETCMD) if (i >= PST_CLEN) { union pstun j; j.pst_command = s; i = PST_CLEN; RSTRING(val)->len = i; *(s + i) = '\0'; pstat(PSTAT_SETCMD, j, PST_CLEN, 0, 0); } else { union pstun j; j.pst_command = s; pstat(PSTAT_SETCMD, j, i, 0, 0); } progname = rb_tainted_str_new(s, i); #elif defined(HAVE_SETPROCTITLE) setproctitle("%.*s", (int)i, s); progname = rb_tainted_str_new(s, i); #else if (len == 0) { len = get_arglen(origargc, origargv); } if (i >= len) { i = len; } memcpy(origargv[0], s, i); s = origargv[0] + i; *s = '\0'; if (++i < len) memset(s + 1, ' ', len - i); for (i = len-1, j = origargc-1; j > 0 && i >= 0; --i, --j) { origargv[j] = origargv[0] + i; *origargv[j] = '\0'; } progname = rb_tainted_str_new2(origargv[0]); #endif rb_progname = rb_obj_freeze(progname); } void ruby_script(name) const char *name; { if (name) { rb_progname = rb_obj_freeze(rb_tainted_str_new2(name)); ruby_sourcefile = rb_source_filename(name); } } static int uid, euid, gid, egid; static void init_ids() { uid = (int)getuid(); euid = (int)geteuid(); gid = (int)getgid(); egid = (int)getegid(); #ifdef VMS uid |= gid << 16; euid |= egid << 16; #endif if (uid && (euid != uid || egid != gid)) { rb_set_safe_level(1); } } static void forbid_setid(s) const char *s; { if (euid != uid) rb_raise(rb_eSecurityError, "no %s allowed while running setuid", s); if (egid != gid) rb_raise(rb_eSecurityError, "no %s allowed while running setgid", s); if (rb_safe_level() > 0) rb_raise(rb_eSecurityError, "no %s allowed in tainted mode", s); } static void verbose_setter(val, id, variable) VALUE val; ID id; VALUE *variable; { ruby_verbose = RTEST(val) ? Qtrue : val; } void ruby_prog_init() { init_ids(); ruby_sourcefile = rb_source_filename("ruby"); rb_define_hooked_variable("$VERBOSE", &ruby_verbose, 0, verbose_setter); rb_define_hooked_variable("$-v", &ruby_verbose, 0, verbose_setter); rb_define_hooked_variable("$-w", &ruby_verbose, 0, verbose_setter); rb_define_variable("$DEBUG", &ruby_debug); rb_define_variable("$-d", &ruby_debug); rb_define_readonly_variable("$-p", &do_print); rb_define_readonly_variable("$-l", &do_line); rb_define_hooked_variable("$0", &rb_progname, 0, set_arg0); rb_define_hooked_variable("$PROGRAM_NAME", &rb_progname, 0, set_arg0); rb_define_readonly_variable("$*", &rb_argv); rb_argv = rb_ary_new(); rb_define_global_const("ARGV", rb_argv); rb_define_readonly_variable("$-a", &do_split); rb_global_variable(&rb_argv0); #ifdef MSDOS /* * There is no way we can refer to them from ruby, so close them to save * space. */ (void)fclose(stdaux); (void)fclose(stdprn); #endif } void ruby_set_argv(argc, argv) int argc; char **argv; { int i; #if defined(USE_DLN_A_OUT) if (origargv) dln_argv0 = origargv[0]; else dln_argv0 = argv[0]; #endif rb_ary_clear(rb_argv); for (i=0; i < argc; i++) { VALUE arg = rb_tainted_str_new2(argv[i]); OBJ_FREEZE(arg); rb_ary_push(rb_argv, arg); } } void ruby_process_options(argc, argv) int argc; char **argv; { origargc = argc; origargv = argv; ruby_script(argv[0]); /* for the time being */ rb_argv0 = rb_progname; #if defined(USE_DLN_A_OUT) dln_argv0 = argv[0]; #endif set_arg0space(); proc_options(argc, argv); if (do_check && ruby_nerrs == 0) { printf("Syntax OK\n"); exit(0); } if (do_print) { rb_parser_append_print(); } if (do_loop) { rb_parser_while_loop(do_line, do_split); } } /********************************************************************** signal.c - $Author: shyouhei $ $Date: 2008-06-29 09:52:47 +0200 (Sun, 29 Jun 2008) $ created at: Tue Dec 20 10:13:44 JST 1994 Copyright (C) 1993-2003 Yukihiro Matsumoto Copyright (C) 2000 Network Applied Communication Laboratory, Inc. Copyright (C) 2000 Information-technology Promotion Agency, Japan **********************************************************************/ #include "ruby.h" #include "rubysig.h" #include #include #ifdef __BEOS__ #undef SIGBUS #endif #if defined HAVE_SIGPROCMASK || defined HAVE_SIGSETMASK #define USE_TRAP_MASK 1 #else #define USE_TRAP_MASK 0 #endif #ifndef NSIG # ifdef DJGPP # define NSIG SIGMAX # else # define NSIG (_SIGMAX + 1) /* For QNX */ # endif #endif static struct signals { char *signm; int signo; } siglist [] = { {"EXIT", 0}, #ifdef SIGHUP {"HUP", SIGHUP}, #endif {"INT", SIGINT}, #ifdef SIGQUIT {"QUIT", SIGQUIT}, #endif #ifdef SIGILL {"ILL", SIGILL}, #endif #ifdef SIGTRAP {"TRAP", SIGTRAP}, #endif #ifdef SIGIOT {"IOT", SIGIOT}, #endif #ifdef SIGABRT {"ABRT", SIGABRT}, #endif #ifdef SIGEMT {"EMT", SIGEMT}, #endif #ifdef SIGFPE {"FPE", SIGFPE}, #endif #ifdef SIGKILL {"KILL", SIGKILL}, #endif #ifdef SIGBUS {"BUS", SIGBUS}, #endif #ifdef SIGSEGV {"SEGV", SIGSEGV}, #endif #ifdef SIGSYS {"SYS", SIGSYS}, #endif #ifdef SIGPIPE {"PIPE", SIGPIPE}, #endif #ifdef SIGALRM {"ALRM", SIGALRM}, #endif #ifdef SIGTERM {"TERM", SIGTERM}, #endif #ifdef SIGURG {"URG", SIGURG}, #endif #ifdef SIGSTOP {"STOP", SIGSTOP}, #endif #ifdef SIGTSTP {"TSTP", SIGTSTP}, #endif #ifdef SIGCONT {"CONT", SIGCONT}, #endif #ifdef SIGCHLD {"CHLD", SIGCHLD}, #endif #ifdef SIGCLD {"CLD", SIGCLD}, #else # ifdef SIGCHLD {"CLD", SIGCHLD}, # endif #endif #ifdef SIGTTIN {"TTIN", SIGTTIN}, #endif #ifdef SIGTTOU {"TTOU", SIGTTOU}, #endif #ifdef SIGIO {"IO", SIGIO}, #endif #ifdef SIGXCPU {"XCPU", SIGXCPU}, #endif #ifdef SIGXFSZ {"XFSZ", SIGXFSZ}, #endif #ifdef SIGVTALRM {"VTALRM", SIGVTALRM}, #endif #ifdef SIGPROF {"PROF", SIGPROF}, #endif #ifdef SIGWINCH {"WINCH", SIGWINCH}, #endif #ifdef SIGUSR1 {"USR1", SIGUSR1}, #endif #ifdef SIGUSR2 {"USR2", SIGUSR2}, #endif #ifdef SIGLOST {"LOST", SIGLOST}, #endif #ifdef SIGMSG {"MSG", SIGMSG}, #endif #ifdef SIGPWR {"PWR", SIGPWR}, #endif #ifdef SIGPOLL {"POLL", SIGPOLL}, #endif #ifdef SIGDANGER {"DANGER", SIGDANGER}, #endif #ifdef SIGMIGRATE {"MIGRATE", SIGMIGRATE}, #endif #ifdef SIGPRE {"PRE", SIGPRE}, #endif #ifdef SIGGRANT {"GRANT", SIGGRANT}, #endif #ifdef SIGRETRACT {"RETRACT", SIGRETRACT}, #endif #ifdef SIGSOUND {"SOUND", SIGSOUND}, #endif #ifdef SIGINFO {"INFO", SIGINFO}, #endif {NULL, 0} }; static int signm2signo(nm) const char *nm; { struct signals *sigs; for (sigs = siglist; sigs->signm; sigs++) if (strcmp(sigs->signm, nm) == 0) return sigs->signo; return 0; } static char* signo2signm(no) int no; { struct signals *sigs; for (sigs = siglist; sigs->signm; sigs++) if (sigs->signo == no) return sigs->signm; return 0; } const char * ruby_signal_name(no) int no; { return signo2signm(no); } /* * call-seq: * SignalException.new(sig) => signal_exception * * Construct a new SignalException object. +sig+ should be a known * signal name, or a signal number. */ static VALUE esignal_init(argc, argv, self) int argc; VALUE *argv; VALUE self; { int argnum = 1; VALUE sig = Qnil; int signo; const char *signm; char tmpnm[(sizeof(int)*CHAR_BIT)/3+4]; if (argc > 0) { sig = argv[0]; if (FIXNUM_P(sig)) argnum = 2; } if (argc < 1 || argnum < argc) { rb_raise(rb_eArgError, "wrong number of arguments (%d for %d)", argc, argnum); } if (argnum == 2) { signo = FIX2INT(sig); if (signo < 0 || signo > NSIG) { rb_raise(rb_eArgError, "invalid signal number (%d)", signo); } if (argc > 1) { sig = argv[1]; } else { signm = signo2signm(signo); if (signm) { snprintf(tmpnm, sizeof(tmpnm), "SIG%s", signm); } else { snprintf(tmpnm, sizeof(tmpnm), "SIG%u", signo); } sig = rb_str_new2(signm = tmpnm); } } else { signm = SYMBOL_P(sig) ? rb_id2name(SYM2ID(sig)) : StringValuePtr(sig); if (strncmp(signm, "SIG", 3) == 0) signm += 3; signo = signm2signo(signm); if (!signo) { rb_raise(rb_eArgError, "unsupported name `SIG%s'", signm); } if (SYMBOL_P(sig)) { sig = rb_str_new2(signm); } } rb_call_super(1, &sig); rb_iv_set(self, "signo", INT2NUM(signo)); return self; } static VALUE interrupt_init(self, mesg) VALUE self, mesg; { VALUE argv[2]; argv[0] = INT2FIX(SIGINT); argv[1] = mesg; return rb_call_super(2, argv); } void ruby_default_signal(sig) int sig; { #ifndef MACOS_UNUSE_SIGNAL extern rb_pid_t getpid _((void)); signal(sig, SIG_DFL); kill(getpid(), sig); #endif } /* * call-seq: * Process.kill(signal, pid, ...) => fixnum * * Sends the given signal to the specified process id(s), or to the * current process if _pid_ is zero. _signal_ may be an * integer signal number or a POSIX signal name (either with or without * a +SIG+ prefix). If _signal_ is negative (or starts * with a minus sign), kills process groups instead of * processes. Not all signals are available on all platforms. * * pid = fork do * Signal.trap("HUP") { puts "Ouch!"; exit } * # ... do some work ... * end * # ... * Process.kill("HUP", pid) * Process.wait * * produces: * * Ouch! */ VALUE rb_f_kill(argc, argv) int argc; VALUE *argv; { int negative = 0; int sig; int i; char *s; rb_secure(2); if (argc < 2) rb_raise(rb_eArgError, "wrong number of arguments -- kill(sig, pid...)"); switch (TYPE(argv[0])) { case T_FIXNUM: sig = FIX2INT(argv[0]); break; case T_SYMBOL: s = rb_id2name(SYM2ID(argv[0])); if (!s) rb_raise(rb_eArgError, "bad signal"); goto str_signal; case T_STRING: s = RSTRING(argv[0])->ptr; if (s[0] == '-') { negative++; s++; } str_signal: if (strncmp("SIG", s, 3) == 0) s += 3; if((sig = signm2signo(s)) == 0) rb_raise(rb_eArgError, "unsupported name `SIG%s'", s); if (negative) sig = -sig; break; default: { VALUE str; str = rb_check_string_type(argv[0]); if (!NIL_P(str)) { s = RSTRING(str)->ptr; goto str_signal; } rb_raise(rb_eArgError, "bad signal type %s", rb_obj_classname(argv[0])); } break; } if (sig < 0) { sig = -sig; for (i=1; i= NSIG) { rb_bug("trap_handler: Bad signal %d", sig); } #if defined(HAVE_NATIVETHREAD) && defined(HAVE_NATIVETHREAD_KILL) if (!is_ruby_native_thread() && !rb_trap_accept_nativethreads[sig]) { sigsend_to_ruby_thread(sig); return; } #endif #if !defined(BSD_SIGNAL) && !defined(POSIX_SIGNAL) if (rb_trap_accept_nativethreads[sig]) { ruby_nativethread_signal(sig, sighandler); } else { ruby_signal(sig, sighandler); } #endif if (trap_list[sig].cmd == 0 && ATOMIC_TEST(rb_trap_immediate)) { IN_MAIN_CONTEXT(signal_exec, sig); ATOMIC_SET(rb_trap_immediate, 1); } else { ATOMIC_INC(rb_trap_pending); ATOMIC_INC(trap_pending_list[sig]); #ifdef _WIN32 rb_w32_interrupted(); #endif } } #ifdef SIGBUS static RETSIGTYPE sigbus _((int)); static RETSIGTYPE sigbus(sig) int sig; { #if defined(HAVE_NATIVETHREAD) && defined(HAVE_NATIVETHREAD_KILL) if (!is_ruby_native_thread() && !rb_trap_accept_nativethreads[sig]) { sigsend_to_ruby_thread(sig); return; } #endif rb_bug("Bus Error"); } #endif #ifdef SIGSEGV static RETSIGTYPE sigsegv _((int)); static RETSIGTYPE sigsegv(sig) int sig; { #if defined(HAVE_NATIVETHREAD) && defined(HAVE_NATIVETHREAD_KILL) if (!is_ruby_native_thread() && !rb_trap_accept_nativethreads[sig]) { sigsend_to_ruby_thread(sig); return; } #endif rb_bug("Segmentation fault"); } #endif #ifdef SIGPIPE static RETSIGTYPE sigpipe _((int)); static RETSIGTYPE sigpipe(sig) int sig; { /* do nothing */ } #endif void rb_trap_exit() { #ifndef MACOS_UNUSE_SIGNAL if (trap_list[0].cmd) { VALUE trap_exit = trap_list[0].cmd; trap_list[0].cmd = 0; rb_eval_cmd(trap_exit, rb_ary_new3(1, INT2FIX(0)), trap_list[0].safe); } #endif } void rb_trap_exec() { #ifndef MACOS_UNUSE_SIGNAL int i; for (i=0; icmd; if (NIL_P(command)) { func = SIG_IGN; } else if (TYPE(command) == T_STRING) { SafeStringValue(command); /* taint check */ if (RSTRING(command)->len == 0) { func = SIG_IGN; } else if (RSTRING(command)->len == 7) { if (strncmp(RSTRING(command)->ptr, "SIG_IGN", 7) == 0) { func = SIG_IGN; } else if (strncmp(RSTRING(command)->ptr, "SIG_DFL", 7) == 0) { func = SIG_DFL; } else if (strncmp(RSTRING(command)->ptr, "DEFAULT", 7) == 0) { func = SIG_DFL; } } else if (RSTRING(command)->len == 6) { if (strncmp(RSTRING(command)->ptr, "IGNORE", 6) == 0) { func = SIG_IGN; } } else if (RSTRING(command)->len == 4) { if (strncmp(RSTRING(command)->ptr, "EXIT", 4) == 0) { func = sigexit; } } } if (func == SIG_IGN || func == SIG_DFL) { command = 0; } switch (TYPE(arg->sig)) { case T_FIXNUM: sig = FIX2INT(arg->sig); break; case T_SYMBOL: s = rb_id2name(SYM2ID(arg->sig)); if (!s) rb_raise(rb_eArgError, "bad signal"); goto str_signal; case T_STRING: s = RSTRING(arg->sig)->ptr; str_signal: if (strncmp("SIG", s, 3) == 0) s += 3; sig = signm2signo(s); if (sig == 0 && strcmp(s, "EXIT") != 0) rb_raise(rb_eArgError, "unsupported signal SIG%s", s); } if (sig < 0 || sig >= NSIG) { rb_raise(rb_eArgError, "invalid signal number (%d)", sig); } #if defined(HAVE_SETITIMER) if (sig == SIGVTALRM) { rb_raise(rb_eArgError, "SIGVTALRM reserved for Thread; can't set handler"); } #endif if (func == SIG_DFL) { switch (sig) { case SIGINT: #ifdef SIGHUP case SIGHUP: #endif #ifdef SIGQUIT case SIGQUIT: #endif #ifdef SIGTERM case SIGTERM: #endif #ifdef SIGALRM case SIGALRM: #endif #ifdef SIGUSR1 case SIGUSR1: #endif #ifdef SIGUSR2 case SIGUSR2: #endif func = sighandler; break; #ifdef SIGBUS case SIGBUS: func = sigbus; break; #endif #ifdef SIGSEGV case SIGSEGV: func = sigsegv; break; #endif #ifdef SIGPIPE case SIGPIPE: func = sigpipe; break; #endif } } oldfunc = ruby_signal(sig, func); oldcmd = trap_list[sig].cmd; if (!oldcmd) { if (oldfunc == SIG_IGN) oldcmd = rb_str_new2("IGNORE"); else if (oldfunc == sighandler) oldcmd = rb_str_new2("DEFAULT"); else oldcmd = Qnil; } trap_list[sig].cmd = command; trap_list[sig].safe = ruby_safe_level; /* enable at least specified signal. */ #if USE_TRAP_MASK #ifdef HAVE_SIGPROCMASK sigdelset(&arg->mask, sig); #else arg->mask &= ~sigmask(sig); #endif #endif return oldcmd; } #if USE_TRAP_MASK static VALUE trap_ensure(arg) struct trap_arg *arg; { /* enable interrupt */ #ifdef HAVE_SIGPROCMASK sigprocmask(SIG_SETMASK, &arg->mask, NULL); #else sigsetmask(arg->mask); #endif trap_last_mask = arg->mask; return 0; } #endif void rb_trap_restore_mask() { #if USE_TRAP_MASK # ifdef HAVE_SIGPROCMASK sigprocmask(SIG_SETMASK, &trap_last_mask, NULL); # else sigsetmask(trap_last_mask); # endif #endif } /* * call-seq: * Signal.trap( signal, proc ) => obj * Signal.trap( signal ) {| | block } => obj * * Specifies the handling of signals. The first parameter is a signal * name (a string such as ``SIGALRM'', ``SIGUSR1'', and so on) or a * signal number. The characters ``SIG'' may be omitted from the * signal name. The command or block specifies code to be run when the * signal is raised. If the command is the string ``IGNORE'' or * ``SIG_IGN'', the signal will be ignored. If the command is * ``DEFAULT'' or ``SIG_DFL'', the operating system's default handler * will be invoked. If the command is ``EXIT'', the script will be * terminated by the signal. Otherwise, the given command or block * will be run. * The special signal name ``EXIT'' or signal number zero will be * invoked just prior to program termination. * trap returns the previous handler for the given signal. * * Signal.trap(0, proc { puts "Terminating: #{$$}" }) * Signal.trap("CLD") { puts "Child died" } * fork && Process.wait * * produces: * Terminating: 27461 * Child died * Terminating: 27460 */ static VALUE sig_trap(argc, argv) int argc; VALUE *argv; { struct trap_arg arg; rb_secure(2); if (argc == 0 || argc > 2) { rb_raise(rb_eArgError, "wrong number of arguments -- trap(sig, cmd)/trap(sig){...}"); } arg.sig = argv[0]; if (argc == 1) { arg.cmd = rb_block_proc(); } else if (argc == 2) { arg.cmd = argv[1]; } if (OBJ_TAINTED(arg.cmd)) { rb_raise(rb_eSecurityError, "Insecure: tainted signal trap"); } #if USE_TRAP_MASK /* disable interrupt */ # ifdef HAVE_SIGPROCMASK sigfillset(&arg.mask); sigprocmask(SIG_BLOCK, &arg.mask, &arg.mask); # else arg.mask = sigblock(~0); # endif return rb_ensure(trap, (VALUE)&arg, trap_ensure, (VALUE)&arg); #else return trap(&arg); #endif } /* * call-seq: * Signal.list => a_hash * * Returns a list of signal names mapped to the corresponding * underlying signal numbers. * * Signal.list #=> {"ABRT"=>6, "ALRM"=>14, "BUS"=>7, "CHLD"=>17, "CLD"=>17, "CONT"=>18, "FPE"=>8, "HUP"=>1, "ILL"=>4, "INT"=>2, "IO"=>29, "IOT"=>6, "KILL"=>9, "PIPE"=>13, "POLL"=>29, "PROF"=>27, "PWR"=>30, "QUIT"=>3, "SEGV"=>11, "STOP"=>19, "SYS"=>31, "TERM"=>15, "TRAP"=>5, "TSTP"=>20, "TTIN"=>21, "TTOU"=>22, "URG"=>23, "USR1"=>10, "USR2"=>12, "VTALRM"=>26, "WINCH"=>28, "XCPU"=>24, "XFSZ"=>25} */ static VALUE sig_list() { VALUE h = rb_hash_new(); struct signals *sigs; for (sigs = siglist; sigs->signm; sigs++) { rb_hash_aset(h, rb_str_new2(sigs->signm), INT2FIX(sigs->signo)); } return h; } static void install_sighandler(signum, handler) int signum; sighandler_t handler; { sighandler_t old; old = ruby_signal(signum, handler); if (old != SIG_DFL) { ruby_signal(signum, old); } } #if 0 /* * If you write a handler which works on any native thread * (even if the thread is NOT a ruby's one), please enable * this function and use it to install the handler, instead * of `install_sighandler()'. */ #ifdef HAVE_NATIVETHREAD static void install_nativethread_sighandler(signum, handler) int signum; sighandler_t handler; { sighandler_t old; int old_st; old_st = rb_trap_accept_nativethreads[signum]; old = ruby_nativethread_signal(signum, handler); if (old != SIG_DFL) { if (old_st) { ruby_nativethread_signal(signum, old); } else { ruby_signal(signum, old); } } } #endif #endif static void init_sigchld(sig) int sig; { sighandler_t oldfunc; #if USE_TRAP_MASK # ifdef HAVE_SIGPROCMASK sigset_t mask; # else int mask; # endif #endif #if USE_TRAP_MASK /* disable interrupt */ # ifdef HAVE_SIGPROCMASK sigfillset(&mask); sigprocmask(SIG_BLOCK, &mask, &mask); # else mask = sigblock(~0); # endif #endif oldfunc = ruby_signal(sig, SIG_DFL); if (oldfunc != SIG_DFL && oldfunc != SIG_IGN) { ruby_signal(sig, oldfunc); } else { trap_list[sig].cmd = 0; } #if USE_TRAP_MASK #ifdef HAVE_SIGPROCMASK sigdelset(&mask, sig); sigprocmask(SIG_SETMASK, &mask, NULL); #else mask &= ~sigmask(sig); sigsetmask(mask); #endif trap_last_mask = mask; #endif } /* * Many operating systems allow signals to be sent to running * processes. Some signals have a defined effect on the process, while * others may be trapped at the code level and acted upon. For * example, your process may trap the USR1 signal and use it to toggle * debugging, and may use TERM to initiate a controlled shutdown. * * pid = fork do * Signal.trap("USR1") do * $debug = !$debug * puts "Debug now: #$debug" * end * Signal.trap("TERM") do * puts "Terminating..." * shutdown() * end * # . . . do some work . . . * end * * Process.detach(pid) * * # Controlling program: * Process.kill("USR1", pid) * # ... * Process.kill("USR1", pid) * # ... * Process.kill("TERM", pid) * * produces: * Debug now: true * Debug now: false * Terminating... * * The list of available signal names and their interpretation is * system dependent. Signal delivery semantics may also vary between * systems; in particular signal delivery may not always be reliable. */ void Init_signal() { #ifndef MACOS_UNUSE_SIGNAL VALUE mSignal = rb_define_module("Signal"); rb_define_global_function("trap", sig_trap, -1); rb_define_module_function(mSignal, "trap", sig_trap, -1); rb_define_module_function(mSignal, "list", sig_list, 0); rb_define_method(rb_eSignal, "initialize", esignal_init, -1); rb_attr(rb_eSignal, rb_intern("signo"), 1, 0, 0); rb_alias(rb_eSignal, rb_intern("signm"), rb_intern("message")); rb_define_method(rb_eInterrupt, "initialize", interrupt_init, 1); install_sighandler(SIGINT, sighandler); #ifdef SIGHUP install_sighandler(SIGHUP, sighandler); #endif #ifdef SIGQUIT install_sighandler(SIGQUIT, sighandler); #endif #ifdef SIGTERM install_sighandler(SIGTERM, sighandler); #endif #ifdef SIGALRM install_sighandler(SIGALRM, sighandler); #endif #ifdef SIGUSR1 install_sighandler(SIGUSR1, sighandler); #endif #ifdef SIGUSR2 install_sighandler(SIGUSR2, sighandler); #endif #ifdef SIGBUS install_sighandler(SIGBUS, sigbus); #endif #ifdef SIGSEGV install_sighandler(SIGSEGV, sigsegv); #endif #ifdef SIGPIPE install_sighandler(SIGPIPE, sigpipe); #endif #if defined(SIGCLD) init_sigchld(SIGCLD); #elif defined(SIGCHLD) init_sigchld(SIGCHLD); #endif #endif /* MACOS_UNUSE_SIGNAL */ } /********************************************************************** sprintf.c - $Author: shyouhei $ $Date: 2008-06-20 01:12:46 +0200 (Fri, 20 Jun 2008) $ created at: Fri Oct 15 10:39:26 JST 1993 Copyright (C) 1993-2003 Yukihiro Matsumoto Copyright (C) 2000 Network Applied Communication Laboratory, Inc. Copyright (C) 2000 Information-technology Promotion Agency, Japan **********************************************************************/ #include "ruby.h" #include "re.h" #include #include #define BIT_DIGITS(N) (((N)*146)/485 + 1) /* log2(10) =~ 146/485 */ #define BITSPERDIG (SIZEOF_BDIGITS*CHAR_BIT) #define EXTENDSIGN(n, l) (((~0 << (n)) >> (((n)*(l)) % BITSPERDIG)) & ~(~0 << (n))) static void fmt_setup _((char*,int,int,int,int)); static char* remove_sign_bits(str, base) char *str; int base; { char *s, *t; s = t = str; if (base == 16) { while (*t == 'f') { t++; } } else if (base == 8) { *t |= EXTENDSIGN(3, strlen(t)); while (*t == '7') { t++; } } else if (base == 2) { while (*t == '1') { t++; } } if (t > s) { while (*t) *s++ = *t++; *s = '\0'; } return str; } static char sign_bits(base, p) int base; const char *p; { char c = '.'; switch (base) { case 16: if (*p == 'X') c = 'F'; else c = 'f'; break; case 8: c = '7'; break; case 2: c = '1'; break; } return c; } #define FNONE 0 #define FSHARP 1 #define FMINUS 2 #define FPLUS 4 #define FZERO 8 #define FSPACE 16 #define FWIDTH 32 #define FPREC 64 #define FPREC0 128 #define CHECK(l) do {\ while (blen + (l) >= bsiz) {\ bsiz*=2;\ }\ rb_str_resize(result, bsiz);\ buf = RSTRING(result)->ptr;\ } while (0) #define PUSH(s, l) do { \ CHECK(l);\ memcpy(&buf[blen], s, l);\ blen += (l);\ } while (0) #define GETARG() (nextvalue != Qundef ? nextvalue : \ posarg < 0 ? \ (rb_raise(rb_eArgError, "unnumbered(%d) mixed with numbered", nextarg), 0) : \ (posarg = nextarg++, GETNTHARG(posarg))) #define GETPOSARG(n) (posarg > 0 ? \ (rb_raise(rb_eArgError, "numbered(%d) after unnumbered(%d)", n, posarg), 0) : \ ((n < 1) ? (rb_raise(rb_eArgError, "invalid index - %d$", n), 0) : \ (posarg = -1, GETNTHARG(n)))) #define GETNTHARG(nth) \ ((nth >= argc) ? (rb_raise(rb_eArgError, "too few arguments"), 0) : argv[nth]) #define GETNUM(n, val) \ for (; p < end && ISDIGIT(*p); p++) { \ int next_n = 10 * n + (*p - '0'); \ if (next_n / 10 != n) {\ rb_raise(rb_eArgError, #val " too big"); \ } \ n = next_n; \ } \ if (p >= end) { \ rb_raise(rb_eArgError, "malformed format string - %%*[0-9]"); \ } #define GETASTER(val) do { \ t = p++; \ n = 0; \ GETNUM(n, val); \ if (*p == '$') { \ tmp = GETPOSARG(n); \ } \ else { \ tmp = GETARG(); \ p = t; \ } \ val = NUM2INT(tmp); \ } while (0) /* * call-seq: * format(format_string [, arguments...] ) => string * sprintf(format_string [, arguments...] ) => string * * Returns the string resulting from applying format_string to * any additional arguments. Within the format string, any characters * other than format sequences are copied to the result. A format * sequence consists of a percent sign, followed by optional flags, * width, and precision indicators, then terminated with a field type * character. The field type controls how the corresponding * sprintf argument is to be interpreted, while the flags * modify that interpretation. The field type characters are listed * in the table at the end of this section. The flag characters are: * * Flag | Applies to | Meaning * ---------+--------------+----------------------------------------- * space | bdeEfgGiouxX | Leave a space at the start of * | | positive numbers. * ---------+--------------+----------------------------------------- * (digit)$ | all | Specifies the absolute argument number * | | for this field. Absolute and relative * | | argument numbers cannot be mixed in a * | | sprintf string. * ---------+--------------+----------------------------------------- * # | beEfgGoxX | Use an alternative format. For the * | | conversions `o', `x', `X', and `b', * | | prefix the result with ``0'', ``0x'', ``0X'', * | | and ``0b'', respectively. For `e', * | | `E', `f', `g', and 'G', force a decimal * | | point to be added, even if no digits follow. * | | For `g' and 'G', do not remove trailing zeros. * ---------+--------------+----------------------------------------- * + | bdeEfgGiouxX | Add a leading plus sign to positive numbers. * ---------+--------------+----------------------------------------- * - | all | Left-justify the result of this conversion. * ---------+--------------+----------------------------------------- * 0 (zero) | bdeEfgGiouxX | Pad with zeros, not spaces. * ---------+--------------+----------------------------------------- * * | all | Use the next argument as the field width. * | | If negative, left-justify the result. If the * | | asterisk is followed by a number and a dollar * | | sign, use the indicated argument as the width. * * * The field width is an optional integer, followed optionally by a * period and a precision. The width specifies the minimum number of * characters that will be written to the result for this field. For * numeric fields, the precision controls the number of decimal places * displayed. For string fields, the precision determines the maximum * number of characters to be copied from the string. (Thus, the format * sequence %10.10s will always contribute exactly ten * characters to the result.) * * The field types are: * * Field | Conversion * ------+-------------------------------------------------------------- * b | Convert argument as a binary number. * c | Argument is the numeric code for a single character. * d | Convert argument as a decimal number. * E | Equivalent to `e', but uses an uppercase E to indicate * | the exponent. * e | Convert floating point argument into exponential notation * | with one digit before the decimal point. The precision * | determines the number of fractional digits (defaulting to six). * f | Convert floating point argument as [-]ddd.ddd, * | where the precision determines the number of digits after * | the decimal point. * G | Equivalent to `g', but use an uppercase `E' in exponent form. * g | Convert a floating point number using exponential form * | if the exponent is less than -4 or greater than or * | equal to the precision, or in d.dddd form otherwise. * i | Identical to `d'. * o | Convert argument as an octal number. * p | The valuing of argument.inspect. * s | Argument is a string to be substituted. If the format * | sequence contains a precision, at most that many characters * | will be copied. * u | Treat argument as an unsigned decimal number. Negative integers * | are displayed as a 32 bit two's complement plus one for the * | underlying architecture; that is, 2 ** 32 + n. However, since * | Ruby has no inherent limit on bits used to represent the * | integer, this value is preceded by two dots (..) in order to * | indicate a infinite number of leading sign bits. * X | Convert argument as a hexadecimal number using uppercase * | letters. Negative numbers will be displayed with two * | leading periods (representing an infinite string of * | leading 'FF's. * x | Convert argument as a hexadecimal number. * | Negative numbers will be displayed with two * | leading periods (representing an infinite string of * | leading 'ff's. * * Examples: * * sprintf("%d %04x", 123, 123) #=> "123 007b" * sprintf("%08b '%4s'", 123, 123) #=> "01111011 ' 123'" * sprintf("%1$*2$s %2$d %1$s", "hello", 8) #=> " hello 8 hello" * sprintf("%1$*2$s %2$d", "hello", -8) #=> "hello -8" * sprintf("%+g:% g:%-g", 1.23, 1.23, 1.23) #=> "+1.23: 1.23:1.23" * sprintf("%u", -123) #=> "..4294967173" */ VALUE rb_f_sprintf(argc, argv) int argc; VALUE *argv; { return rb_str_format(argc - 1, argv + 1, GETNTHARG(0)); } VALUE rb_str_format(argc, argv, fmt) int argc; VALUE *argv; VALUE fmt; { const char *p, *end; char *buf; int blen, bsiz; VALUE result; int width, prec, flags = FNONE; int nextarg = 1; int posarg = 0; int tainted = 0; VALUE nextvalue; VALUE tmp; VALUE str; #define CHECK_FOR_WIDTH(f) \ if ((f) & FWIDTH) { \ rb_raise(rb_eArgError, "width given twice"); \ } \ if ((f) & FPREC0) { \ rb_raise(rb_eArgError, "width after precision"); \ } #define CHECK_FOR_FLAGS(f) \ if ((f) & FWIDTH) { \ rb_raise(rb_eArgError, "flag after width"); \ } \ if ((f) & FPREC0) { \ rb_raise(rb_eArgError, "flag after precision"); \ } ++argc; --argv; if (OBJ_TAINTED(fmt)) tainted = 1; StringValue(fmt); fmt = rb_str_new4(fmt); p = RSTRING(fmt)->ptr; end = p + RSTRING(fmt)->len; blen = 0; bsiz = 120; result = rb_str_buf_new(bsiz); buf = RSTRING(result)->ptr; for (; p < end; p++) { const char *t; int n; for (t = p; t < end && *t != '%'; t++) ; PUSH(p, t - p); if (t >= end) { /* end of fmt string */ goto sprint_exit; } p = t + 1; /* skip `%' */ width = prec = -1; nextvalue = Qundef; retry: switch (*p) { default: if (ISPRINT(*p)) rb_raise(rb_eArgError, "malformed format string - %%%c", *p); else rb_raise(rb_eArgError, "malformed format string"); break; case ' ': CHECK_FOR_FLAGS(flags); flags |= FSPACE; p++; goto retry; case '#': CHECK_FOR_FLAGS(flags); flags |= FSHARP; p++; goto retry; case '+': CHECK_FOR_FLAGS(flags); flags |= FPLUS; p++; goto retry; case '-': CHECK_FOR_FLAGS(flags); flags |= FMINUS; p++; goto retry; case '0': CHECK_FOR_FLAGS(flags); flags |= FZERO; p++; goto retry; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': n = 0; GETNUM(n, width); if (*p == '$') { if (nextvalue != Qundef) { rb_raise(rb_eArgError, "value given twice - %d$", n); } nextvalue = GETPOSARG(n); p++; goto retry; } CHECK_FOR_WIDTH(flags); width = n; flags |= FWIDTH; goto retry; case '*': CHECK_FOR_WIDTH(flags); flags |= FWIDTH; GETASTER(width); if (width < 0) { flags |= FMINUS; width = -width; } p++; goto retry; case '.': if (flags & FPREC0) { rb_raise(rb_eArgError, "precision given twice"); } flags |= FPREC|FPREC0; prec = 0; p++; if (*p == '*') { GETASTER(prec); if (prec < 0) { /* ignore negative precision */ flags &= ~FPREC; } p++; goto retry; } GETNUM(prec, precision); goto retry; case '\n': case '\0': p--; case '%': if (flags != FNONE) { rb_raise(rb_eArgError, "illegal format character - %%"); } PUSH("%", 1); break; case 'c': { VALUE val = GETARG(); char c; if (!(flags & FMINUS)) while (--width > 0) PUSH(" ", 1); c = NUM2INT(val) & 0xff; PUSH(&c, 1); while (--width > 0) PUSH(" ", 1); } break; case 's': case 'p': { VALUE arg = GETARG(); long len; if (*p == 'p') arg = rb_inspect(arg); str = rb_obj_as_string(arg); if (OBJ_TAINTED(str)) tainted = 1; len = RSTRING(str)->len; if (flags&FPREC) { if (prec < len) { len = prec; } } /* need to adjust multi-byte string pos */ if (flags&FWIDTH) { if (width > len) { CHECK(width); width -= len; if (!(flags&FMINUS)) { while (width--) { buf[blen++] = ' '; } } memcpy(&buf[blen], RSTRING_PTR(str), len); blen += len; if (flags&FMINUS) { while (width--) { buf[blen++] = ' '; } } break; } } PUSH(RSTRING(str)->ptr, len); } break; case 'd': case 'i': case 'o': case 'x': case 'X': case 'b': case 'B': case 'u': { volatile VALUE val = GETARG(); char fbuf[32], nbuf[64], *s, *t; const char *prefix = 0; int sign = 0; char sc = 0; long v = 0; int base, bignum = 0; int len, pos; volatile VALUE tmp; volatile VALUE tmp1; switch (*p) { case 'd': case 'i': sign = 1; break; case 'o': case 'x': case 'X': case 'b': case 'B': case 'u': default: if (flags&(FPLUS|FSPACE)) sign = 1; break; } if (flags & FSHARP) { switch (*p) { case 'o': prefix = "0"; break; case 'x': prefix = "0x"; break; case 'X': prefix = "0X"; break; case 'b': prefix = "0b"; break; case 'B': prefix = "0B"; break; } if (prefix) { width -= strlen(prefix); } } bin_retry: switch (TYPE(val)) { case T_FLOAT: val = rb_dbl2big(RFLOAT(val)->value); if (FIXNUM_P(val)) goto bin_retry; bignum = 1; break; case T_STRING: val = rb_str_to_inum(val, 0, Qtrue); goto bin_retry; case T_BIGNUM: bignum = 1; break; case T_FIXNUM: v = FIX2LONG(val); break; default: val = rb_Integer(val); goto bin_retry; } switch (*p) { case 'o': base = 8; break; case 'x': case 'X': base = 16; break; case 'b': case 'B': base = 2; break; case 'u': case 'd': case 'i': default: base = 10; break; } if (!bignum) { if (base == 2) { val = rb_int2big(v); goto bin_retry; } if (sign) { char c = *p; if (c == 'i') c = 'd'; /* %d and %i are identical */ if (v < 0) { v = -v; sc = '-'; width--; } else if (flags & FPLUS) { sc = '+'; width--; } else if (flags & FSPACE) { sc = ' '; width--; } sprintf(fbuf, "%%l%c", c); sprintf(nbuf, fbuf, v); s = nbuf; goto format_integer; } s = nbuf; if (v < 0) { if (base == 10) { rb_warning("negative number for %%u specifier"); } if (!(flags&(FPREC|FZERO))) { strcpy(s, ".."); s += 2; } } sprintf(fbuf, "%%l%c", *p == 'X' ? 'x' : *p); sprintf(s, fbuf, v); if (v < 0) { char d = 0; remove_sign_bits(s, base); switch (base) { case 16: d = 'f'; break; case 8: d = '7'; break; } if (d && *s != d) { memmove(s+1, s, strlen(s)+1); *s = d; } } s = nbuf; goto format_integer; } if (sign) { tmp = rb_big2str(val, base); s = RSTRING(tmp)->ptr; if (s[0] == '-') { s++; sc = '-'; width--; } else if (flags & FPLUS) { sc = '+'; width--; } else if (flags & FSPACE) { sc = ' '; width--; } goto format_integer; } if (!RBIGNUM(val)->sign) { val = rb_big_clone(val); rb_big_2comp(val); } tmp1 = tmp = rb_big2str0(val, base, RBIGNUM(val)->sign); s = RSTRING(tmp)->ptr; if (*s == '-') { if (base == 10) { rb_warning("negative number for %%u specifier"); } remove_sign_bits(++s, base); tmp = rb_str_new(0, 3+strlen(s)); t = RSTRING(tmp)->ptr; if (!(flags&(FPREC|FZERO))) { strcpy(t, ".."); t += 2; } switch (base) { case 16: if (s[0] != 'f') strcpy(t++, "f"); break; case 8: if (s[0] != '7') strcpy(t++, "7"); break; case 2: if (s[0] != '1') strcpy(t++, "1"); break; } strcpy(t, s); bignum = 2; } s = RSTRING(tmp)->ptr; format_integer: pos = -1; len = strlen(s); if (*p == 'X') { char *pp = s; while (*pp) { *pp = toupper(*pp); pp++; } } if ((flags&(FZERO|FPREC)) == FZERO) { prec = width; width = 0; } else { if (prec < len) prec = len; width -= prec; } if (!(flags&FMINUS)) { CHECK(width); while (width-- > 0) { buf[blen++] = ' '; } } if (sc) PUSH(&sc, 1); if (prefix) { int plen = strlen(prefix); PUSH(prefix, plen); } CHECK(prec - len); if (!bignum && v < 0) { char c = sign_bits(base, p); while (len < prec--) { buf[blen++] = c; } } else { char c; if (!sign && bignum && !RBIGNUM(val)->sign) c = sign_bits(base, p); else c = '0'; while (len < prec--) { buf[blen++] = c; } } PUSH(s, len); CHECK(width); while (width-- > 0) { buf[blen++] = ' '; } } break; case 'f': case 'g': case 'G': case 'e': case 'E': { VALUE val = GETARG(); double fval; int i, need = 6; char fbuf[32]; fval = RFLOAT(rb_Float(val))->value; #if defined(_WIN32) && !defined(__BORLANDC__) if (isnan(fval) || isinf(fval)) { char *expr; if (isnan(fval)) { expr = "NaN"; } else { expr = "Inf"; } need = strlen(expr); if ((!isnan(fval) && fval < 0.0) || (flags & FPLUS)) need++; else if (flags & FSPACE) need++; if ((flags & FWIDTH) && need < width) need = width; CHECK(need); sprintf(&buf[blen], "%*s", need, ""); if (flags & FMINUS) { if (!isnan(fval) && fval < 0.0) buf[blen++] = '-'; else if (flags & FPLUS) buf[blen++] = '+'; else if (flags & FSPACE) blen++; strncpy(&buf[blen], expr, strlen(expr)); } else if (flags & FZERO) { if (!isnan(fval) && fval < 0.0) { buf[blen++] = '-'; need--; } else if (flags & FPLUS) { buf[blen++] = '+'; need--; } else if (flags & FSPACE) { blen++; need--; } while (need-- - strlen(expr) > 0) { buf[blen++] = '0'; } strncpy(&buf[blen], expr, strlen(expr)); } else { if (!isnan(fval) && fval < 0.0) buf[blen + need - strlen(expr) - 1] = '-'; else if (flags & FPLUS) buf[blen + need - strlen(expr) - 1] = '+'; strncpy(&buf[blen + need - strlen(expr)], expr, strlen(expr)); } blen += strlen(&buf[blen]); break; } #endif /* defined(_WIN32) && !defined(__BORLANDC__) */ fmt_setup(fbuf, *p, flags, width, prec); need = 0; if (*p != 'e' && *p != 'E') { i = INT_MIN; frexp(fval, &i); if (i > 0) need = BIT_DIGITS(i); } need += (flags&FPREC) ? prec : 6; if ((flags&FWIDTH) && need < width) need = width; need += 20; CHECK(need); sprintf(&buf[blen], fbuf, fval); blen += strlen(&buf[blen]); } break; } flags = FNONE; } sprint_exit: /* XXX - We cannot validiate the number of arguments if (digit)$ style used. */ if (posarg >= 0 && nextarg < argc) { const char *mesg = "too many arguments for format string"; if (RTEST(ruby_debug)) rb_raise(rb_eArgError, mesg); if (RTEST(ruby_verbose)) rb_warn(mesg); } rb_str_resize(result, blen); if (tainted) OBJ_TAINT(result); return result; } static void fmt_setup(buf, c, flags, width, prec) char *buf; int c; int flags, width, prec; { *buf++ = '%'; if (flags & FSHARP) *buf++ = '#'; if (flags & FPLUS) *buf++ = '+'; if (flags & FMINUS) *buf++ = '-'; if (flags & FZERO) *buf++ = '0'; if (flags & FSPACE) *buf++ = ' '; if (flags & FWIDTH) { sprintf(buf, "%d", width); buf += strlen(buf); } if (flags & FPREC) { sprintf(buf, ".%d", prec); buf += strlen(buf); } *buf++ = c; *buf = '\0'; } /* This is a public domain general purpose hash table package written by Peter Moore @ UCB. */ /* static char sccsid[] = "@(#) st.c 5.1 89/12/14 Crucible"; */ #include "config.h" #include "defines.h" #include #ifdef HAVE_STDLIB_H #include #endif #include #include "st.h" typedef struct st_table_entry st_table_entry; struct st_table_entry { unsigned int hash; st_data_t key; st_data_t record; st_table_entry *next; }; #define ST_DEFAULT_MAX_DENSITY 5 #define ST_DEFAULT_INIT_TABLE_SIZE 11 /* * DEFAULT_MAX_DENSITY is the default for the largest we allow the * average number of items per bin before increasing the number of * bins * * DEFAULT_INIT_TABLE_SIZE is the default for the number of bins * allocated initially * */ static int numcmp(long, long); static int numhash(long); static struct st_hash_type type_numhash = { numcmp, numhash, }; /* extern int strcmp(const char *, const char *); */ static int strhash(const char *); static struct st_hash_type type_strhash = { strcmp, strhash, }; static void rehash(st_table *); #ifdef RUBY #define malloc xmalloc #define calloc xcalloc #endif #define alloc(type) (type*)malloc((unsigned)sizeof(type)) #define Calloc(n,s) (char*)calloc((n),(s)) #define EQUAL(table,x,y) ((x)==(y) || (*table->type->compare)((x),(y)) == 0) #define do_hash(key,table) (unsigned int)(*(table)->type->hash)((key)) #define do_hash_bin(key,table) (do_hash(key, table)%(table)->num_bins) /* * MINSIZE is the minimum size of a dictionary. */ #define MINSIZE 8 /* Table of prime numbers 2^n+a, 2<=n<=30. */ static long primes[] = { 8 + 3, 16 + 3, 32 + 5, 64 + 3, 128 + 3, 256 + 27, 512 + 9, 1024 + 9, 2048 + 5, 4096 + 3, 8192 + 27, 16384 + 43, 32768 + 3, 65536 + 45, 131072 + 29, 262144 + 3, 524288 + 21, 1048576 + 7, 2097152 + 17, 4194304 + 15, 8388608 + 9, 16777216 + 43, 33554432 + 35, 67108864 + 15, 134217728 + 29, 268435456 + 3, 536870912 + 11, 1073741824 + 85, 0 }; static int new_size(size) int size; { int i; #if 0 for (i=3; i<31; i++) { if ((1< size) return 1< size) return primes[i]; } /* Ran out of polynomials */ return -1; /* should raise exception */ #endif } #ifdef HASH_LOG static int collision = 0; static int init_st = 0; static void stat_col() { FILE *f = fopen("/tmp/col", "w"); fprintf(f, "collision: %d\n", collision); fclose(f); } #endif st_table* st_init_table_with_size(type, size) struct st_hash_type *type; int size; { st_table *tbl; #ifdef HASH_LOG if (init_st == 0) { init_st = 1; atexit(stat_col); } #endif size = new_size(size); /* round up to prime number */ tbl = alloc(st_table); tbl->type = type; tbl->num_entries = 0; tbl->num_bins = size; tbl->bins = (st_table_entry **)Calloc(size, sizeof(st_table_entry*)); return tbl; } st_table* st_init_table(type) struct st_hash_type *type; { return st_init_table_with_size(type, 0); } st_table* st_init_numtable(void) { return st_init_table(&type_numhash); } st_table* st_init_numtable_with_size(size) int size; { return st_init_table_with_size(&type_numhash, size); } st_table* st_init_strtable(void) { return st_init_table(&type_strhash); } st_table* st_init_strtable_with_size(size) int size; { return st_init_table_with_size(&type_strhash, size); } void st_free_table(table) st_table *table; { register st_table_entry *ptr, *next; int i; for(i = 0; i < table->num_bins; i++) { ptr = table->bins[i]; while (ptr != 0) { next = ptr->next; free(ptr); ptr = next; } } free(table->bins); free(table); } #define PTR_NOT_EQUAL(table, ptr, hash_val, key) \ ((ptr) != 0 && (ptr->hash != (hash_val) || !EQUAL((table), (key), (ptr)->key))) #ifdef HASH_LOG #define COLLISION collision++ #else #define COLLISION #endif #define FIND_ENTRY(table, ptr, hash_val, bin_pos) do {\ bin_pos = hash_val%(table)->num_bins;\ ptr = (table)->bins[bin_pos];\ if (PTR_NOT_EQUAL(table, ptr, hash_val, key)) {\ COLLISION;\ while (PTR_NOT_EQUAL(table, ptr->next, hash_val, key)) {\ ptr = ptr->next;\ }\ ptr = ptr->next;\ }\ } while (0) int st_lookup(table, key, value) st_table *table; register st_data_t key; st_data_t *value; { unsigned int hash_val, bin_pos; register st_table_entry *ptr; hash_val = do_hash(key, table); FIND_ENTRY(table, ptr, hash_val, bin_pos); if (ptr == 0) { return 0; } else { if (value != 0) *value = ptr->record; return 1; } } #define ADD_DIRECT(table, key, value, hash_val, bin_pos)\ do {\ st_table_entry *entry;\ if (table->num_entries/(table->num_bins) > ST_DEFAULT_MAX_DENSITY) {\ rehash(table);\ bin_pos = hash_val % table->num_bins;\ }\ \ entry = alloc(st_table_entry);\ \ entry->hash = hash_val;\ entry->key = key;\ entry->record = value;\ entry->next = table->bins[bin_pos];\ table->bins[bin_pos] = entry;\ table->num_entries++;\ } while (0) int st_insert(table, key, value) register st_table *table; register st_data_t key; st_data_t value; { unsigned int hash_val, bin_pos; register st_table_entry *ptr; hash_val = do_hash(key, table); FIND_ENTRY(table, ptr, hash_val, bin_pos); if (ptr == 0) { ADD_DIRECT(table, key, value, hash_val, bin_pos); return 0; } else { ptr->record = value; return 1; } } void st_add_direct(table, key, value) st_table *table; st_data_t key; st_data_t value; { unsigned int hash_val, bin_pos; hash_val = do_hash(key, table); bin_pos = hash_val % table->num_bins; ADD_DIRECT(table, key, value, hash_val, bin_pos); } static void rehash(table) register st_table *table; { register st_table_entry *ptr, *next, **new_bins; int i, old_num_bins = table->num_bins, new_num_bins; unsigned int hash_val; new_num_bins = new_size(old_num_bins+1); new_bins = (st_table_entry**)Calloc(new_num_bins, sizeof(st_table_entry*)); for(i = 0; i < old_num_bins; i++) { ptr = table->bins[i]; while (ptr != 0) { next = ptr->next; hash_val = ptr->hash % new_num_bins; ptr->next = new_bins[hash_val]; new_bins[hash_val] = ptr; ptr = next; } } free(table->bins); table->num_bins = new_num_bins; table->bins = new_bins; } st_table* st_copy(old_table) st_table *old_table; { st_table *new_table; st_table_entry *ptr, *entry; int i, num_bins = old_table->num_bins; new_table = alloc(st_table); if (new_table == 0) { return 0; } *new_table = *old_table; new_table->bins = (st_table_entry**) Calloc((unsigned)num_bins, sizeof(st_table_entry*)); if (new_table->bins == 0) { free(new_table); return 0; } for(i = 0; i < num_bins; i++) { new_table->bins[i] = 0; ptr = old_table->bins[i]; while (ptr != 0) { entry = alloc(st_table_entry); if (entry == 0) { free(new_table->bins); free(new_table); return 0; } *entry = *ptr; entry->next = new_table->bins[i]; new_table->bins[i] = entry; ptr = ptr->next; } } return new_table; } int st_delete(table, key, value) register st_table *table; register st_data_t *key; st_data_t *value; { unsigned int hash_val; st_table_entry *tmp; register st_table_entry *ptr; hash_val = do_hash_bin(*key, table); ptr = table->bins[hash_val]; if (ptr == 0) { if (value != 0) *value = 0; return 0; } if (EQUAL(table, *key, ptr->key)) { table->bins[hash_val] = ptr->next; table->num_entries--; if (value != 0) *value = ptr->record; *key = ptr->key; free(ptr); return 1; } for(; ptr->next != 0; ptr = ptr->next) { if (EQUAL(table, ptr->next->key, *key)) { tmp = ptr->next; ptr->next = ptr->next->next; table->num_entries--; if (value != 0) *value = tmp->record; *key = tmp->key; free(tmp); return 1; } } return 0; } int st_delete_safe(table, key, value, never) register st_table *table; register st_data_t *key; st_data_t *value; st_data_t never; { unsigned int hash_val; register st_table_entry *ptr; hash_val = do_hash_bin(*key, table); ptr = table->bins[hash_val]; if (ptr == 0) { if (value != 0) *value = 0; return 0; } for(; ptr != 0; ptr = ptr->next) { if ((ptr->key != never) && EQUAL(table, ptr->key, *key)) { table->num_entries--; *key = ptr->key; if (value != 0) *value = ptr->record; ptr->key = ptr->record = never; return 1; } } return 0; } static int delete_never(key, value, never) st_data_t key, value, never; { if (value == never) return ST_DELETE; return ST_CONTINUE; } void st_cleanup_safe(table, never) st_table *table; st_data_t never; { int num_entries = table->num_entries; st_foreach(table, delete_never, never); table->num_entries = num_entries; } int st_foreach(table, func, arg) st_table *table; int (*func)(); st_data_t arg; { st_table_entry *ptr, *last, *tmp; enum st_retval retval; int i; for(i = 0; i < table->num_bins; i++) { last = 0; for(ptr = table->bins[i]; ptr != 0;) { retval = (*func)(ptr->key, ptr->record, arg); switch (retval) { case ST_CHECK: /* check if hash is modified during iteration */ tmp = 0; if (i < table->num_bins) { for (tmp = table->bins[i]; tmp; tmp=tmp->next) { if (tmp == ptr) break; } } if (!tmp) { /* call func with error notice */ return 1; } /* fall through */ case ST_CONTINUE: last = ptr; ptr = ptr->next; break; case ST_STOP: return 0; case ST_DELETE: tmp = ptr; if (last == 0) { table->bins[i] = ptr->next; } else { last->next = ptr->next; } ptr = ptr->next; free(tmp); table->num_entries--; } } } return 0; } static int strhash(string) register const char *string; { register int c; #ifdef HASH_ELFHASH register unsigned int h = 0, g; while ((c = *string++) != '\0') { h = ( h << 4 ) + c; if ( g = h & 0xF0000000 ) h ^= g >> 24; h &= ~g; } return h; #elif defined(HASH_PERL) register int val = 0; while ((c = *string++) != '\0') { val += c; val += (val << 10); val ^= (val >> 6); } val += (val << 3); val ^= (val >> 11); return val + (val << 15); #else register int val = 0; while ((c = *string++) != '\0') { val = val*997 + c; } return val + (val>>5); #endif } static int numcmp(x, y) long x, y; { return x != y; } static int numhash(n) long n; { return n; } /********************************************************************** string.c - $Author: shyouhei $ $Date: 2009-02-17 03:58:28 +0100 (Tue, 17 Feb 2009) $ created at: Mon Aug 9 17:12:58 JST 1993 Copyright (C) 1993-2003 Yukihiro Matsumoto Copyright (C) 2000 Network Applied Communication Laboratory, Inc. Copyright (C) 2000 Information-technology Promotion Agency, Japan **********************************************************************/ #include "ruby.h" #include "re.h" #define BEG(no) regs->beg[no] #define END(no) regs->end[no] #include #include #ifdef HAVE_UNISTD_H #include #endif VALUE rb_cString; #define STR_TMPLOCK FL_USER1 #define STR_ASSOC FL_USER3 #define STR_NOCAPA (ELTS_SHARED|STR_ASSOC) #define RESIZE_CAPA(str,capacity) do {\ REALLOC_N(RSTRING(str)->ptr, char, (capacity)+1);\ if (!FL_TEST(str, STR_NOCAPA))\ RSTRING(str)->aux.capa = (capacity);\ } while (0) VALUE rb_fs; static inline void str_mod_check(s, p, len) VALUE s; char *p; long len; { if (RSTRING(s)->ptr != p || RSTRING(s)->len != len) { rb_raise(rb_eRuntimeError, "string modified"); } } static inline void str_frozen_check(s) VALUE s; { if (OBJ_FROZEN(s)) { rb_raise(rb_eRuntimeError, "string frozen"); } } static VALUE str_alloc _((VALUE)); static VALUE str_alloc(klass) VALUE klass; { NEWOBJ(str, struct RString); OBJSETUP(str, klass, T_STRING); str->ptr = 0; str->len = 0; str->aux.capa = 0; return (VALUE)str; } static VALUE str_new(klass, ptr, len) 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); RSTRING(str)->len = len; RSTRING(str)->aux.capa = len; RSTRING(str)->ptr = ALLOC_N(char,len+1); if (ptr) { memcpy(RSTRING(str)->ptr, ptr, len); } RSTRING(str)->ptr[len] = '\0'; return str; } VALUE rb_str_new(ptr, len) const char *ptr; long len; { return str_new(rb_cString, ptr, len); } VALUE rb_str_new2(ptr) const char *ptr; { if (!ptr) { rb_raise(rb_eArgError, "NULL pointer given"); } return rb_str_new(ptr, strlen(ptr)); } VALUE rb_tainted_str_new(ptr, len) const char *ptr; long len; { VALUE str = rb_str_new(ptr, len); OBJ_TAINT(str); return str; } VALUE rb_tainted_str_new2(ptr) const char *ptr; { VALUE str = rb_str_new2(ptr); OBJ_TAINT(str); return str; } static VALUE str_new3(klass, str) VALUE klass, str; { VALUE str2 = str_alloc(klass); RSTRING(str2)->len = RSTRING(str)->len; RSTRING(str2)->ptr = RSTRING(str)->ptr; RSTRING(str2)->aux.shared = str; FL_SET(str2, ELTS_SHARED); return str2; } VALUE rb_str_new3(str) VALUE str; { VALUE str2 = str_new3(rb_obj_class(str), str); OBJ_INFECT(str2, str); return str2; } static VALUE str_new4(klass, str) VALUE klass, str; { VALUE str2 = str_alloc(klass); RSTRING(str2)->len = RSTRING(str)->len; RSTRING(str2)->ptr = RSTRING(str)->ptr; if (FL_TEST(str, ELTS_SHARED)) { FL_SET(str2, ELTS_SHARED); RSTRING(str2)->aux.shared = RSTRING(str)->aux.shared; } else { FL_SET(str, ELTS_SHARED); RSTRING(str)->aux.shared = str2; } return str2; } VALUE rb_str_new4(orig) VALUE orig; { VALUE klass, str; if (OBJ_FROZEN(orig)) return orig; klass = rb_obj_class(orig); if (FL_TEST(orig, ELTS_SHARED) && (str = RSTRING(orig)->aux.shared) && klass == RBASIC(str)->klass) { long ofs; ofs = RSTRING(str)->len - RSTRING(orig)->len; if ((ofs > 0) || (!OBJ_TAINTED(str) && OBJ_TAINTED(orig))) { str = str_new3(klass, str); RSTRING(str)->ptr += ofs; RSTRING(str)->len -= ofs; } } else if (FL_TEST(orig, STR_ASSOC)) { str = str_new(klass, RSTRING(orig)->ptr, RSTRING(orig)->len); } else { str = str_new4(klass, orig); } OBJ_INFECT(str, orig); OBJ_FREEZE(str); return str; } VALUE rb_str_new5(obj, ptr, len) 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(capa) long capa; { VALUE str = str_alloc(rb_cString); if (capa < STR_BUF_MIN_SIZE) { capa = STR_BUF_MIN_SIZE; } RSTRING(str)->ptr = 0; RSTRING(str)->len = 0; RSTRING(str)->aux.capa = capa; RSTRING(str)->ptr = ALLOC_N(char, capa+1); RSTRING(str)->ptr[0] = '\0'; return str; } VALUE rb_str_buf_new2(ptr) 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_to_str(str) VALUE str; { return rb_convert_type(str, T_STRING, "String", "to_str"); } static void rb_str_shared_replace(str, str2) VALUE str, str2; { if (str == str2) return; rb_str_modify(str); if (!FL_TEST(str, ELTS_SHARED)) free(RSTRING(str)->ptr); if (NIL_P(str2)) { RSTRING(str)->ptr = 0; RSTRING(str)->len = 0; RSTRING(str)->aux.capa = 0; FL_UNSET(str, STR_NOCAPA); return; } RSTRING(str)->ptr = RSTRING(str2)->ptr; RSTRING(str)->len = RSTRING(str2)->len; FL_UNSET(str, STR_NOCAPA); if (FL_TEST(str2, STR_NOCAPA)) { FL_SET(str, RBASIC(str2)->flags & STR_NOCAPA); RSTRING(str)->aux.shared = RSTRING(str2)->aux.shared; } else { RSTRING(str)->aux.capa = RSTRING(str2)->aux.capa; } RSTRING(str2)->ptr = 0; /* abandon str2 */ RSTRING(str2)->len = 0; RSTRING(str2)->aux.capa = 0; FL_UNSET(str2, STR_NOCAPA); if (OBJ_TAINTED(str2)) OBJ_TAINT(str); } static ID id_to_s; VALUE rb_obj_as_string(obj) 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_s_alloc _((VALUE)); static VALUE rb_str_replace _((VALUE, VALUE)); VALUE rb_str_dup(str) 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(argc, argv, str) int argc; VALUE *argv; VALUE str; { VALUE orig; if (rb_scan_args(argc, argv, "01", &orig) == 1) rb_str_replace(str, orig); return str; } /* * call-seq: * str.length => integer * * Returns the length of str. */ static VALUE rb_str_length(str) VALUE str; { return LONG2NUM(RSTRING(str)->len); } /* * 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(str) VALUE str; { if (RSTRING(str)->len == 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(str1, str2) VALUE str1, str2; { VALUE str3; StringValue(str2); str3 = rb_str_new(0, RSTRING(str1)->len+RSTRING(str2)->len); memcpy(RSTRING(str3)->ptr, RSTRING(str1)->ptr, RSTRING(str1)->len); memcpy(RSTRING(str3)->ptr + RSTRING(str1)->len, RSTRING(str2)->ptr, RSTRING(str2)->len); RSTRING(str3)->ptr[RSTRING(str3)->len] = '\0'; if (OBJ_TAINTED(str1) || OBJ_TAINTED(str2)) OBJ_TAINT(str3); 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(str, times) VALUE str; VALUE times; { VALUE str2; long i, len; len = NUM2LONG(times); if (len < 0) { rb_raise(rb_eArgError, "negative argument"); } if (len && LONG_MAX/len < RSTRING(str)->len) { rb_raise(rb_eArgError, "argument too big"); } str2 = rb_str_new5(str,0, len *= RSTRING(str)->len); for (i = 0; i < len; i += RSTRING(str)->len) { memcpy(RSTRING(str2)->ptr + i, RSTRING(str)->ptr, RSTRING(str)->len); } RSTRING(str2)->ptr[RSTRING(str2)->len] = '\0'; OBJ_INFECT(str2, str); 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(str, arg) VALUE str, arg; { volatile 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 const char null_str[] = ""; static int str_independent(str) 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"); if (RSTRING(str)->ptr == null_str) return 0; if (!FL_TEST(str, ELTS_SHARED)) return 1; return 0; } static void str_make_independent(str) VALUE str; { char *ptr; ptr = ALLOC_N(char, RSTRING(str)->len+1); if (RSTRING(str)->ptr) { memcpy(ptr, RSTRING(str)->ptr, RSTRING(str)->len); } ptr[RSTRING(str)->len] = 0; RSTRING(str)->ptr = ptr; RSTRING(str)->aux.capa = RSTRING(str)->len; FL_UNSET(str, STR_NOCAPA); } void rb_str_modify(str) VALUE str; { if (!str_independent(str)) str_make_independent(str); } void rb_str_associate(str, add) VALUE str, add; { if (FL_TEST(str, STR_ASSOC)) { /* already associated */ rb_ary_concat(RSTRING(str)->aux.shared, add); } else { if (FL_TEST(str, ELTS_SHARED)) { str_make_independent(str); } else if (RSTRING(str)->aux.capa != RSTRING(str)->len) { RESIZE_CAPA(str, RSTRING(str)->len); } RSTRING(str)->aux.shared = add; FL_SET(str, STR_ASSOC); } } VALUE rb_str_associated(str) VALUE str; { if (FL_TEST(str, STR_ASSOC)) { return RSTRING(str)->aux.shared; } return Qfalse; } #define make_null_str(s) do { \ FL_SET(s, ELTS_SHARED); \ RSTRING(s)->ptr = (char *)null_str; \ RSTRING(s)->aux.shared = 0; \ } while (0) static VALUE rb_str_s_alloc(klass) VALUE klass; { VALUE str = str_alloc(klass); make_null_str(str); return str; } VALUE rb_string_value(ptr) volatile VALUE *ptr; { VALUE s = *ptr; if (TYPE(s) != T_STRING) { s = rb_str_to_str(s); *ptr = s; } if (!RSTRING(s)->ptr) { make_null_str(s); } return s; } char * rb_string_value_ptr(ptr) volatile VALUE *ptr; { return RSTRING(rb_string_value(ptr))->ptr; } char * rb_string_value_cstr(ptr) volatile VALUE *ptr; { VALUE str = rb_string_value(ptr); char *s = RSTRING(str)->ptr; if (!s || RSTRING(str)->len != strlen(s)) { rb_raise(rb_eArgError, "string contains null byte"); } return s; } VALUE rb_check_string_type(str) VALUE str; { str = rb_check_convert_type(str, T_STRING, "String", "to_str"); if (!NIL_P(str) && !RSTRING(str)->ptr) { make_null_str(str); } return str; } VALUE rb_str_substr(str, beg, len) VALUE str; long beg, len; { VALUE str2; if (len < 0) return Qnil; if (beg > RSTRING(str)->len) return Qnil; if (beg < 0) { beg += RSTRING(str)->len; if (beg < 0) return Qnil; } if (beg + len > RSTRING(str)->len) { len = RSTRING(str)->len - beg; } if (len < 0) { len = 0; } if (len == 0) { str2 = rb_str_new5(str,0,0); } else if (len > sizeof(struct RString)/2 && beg + len == RSTRING(str)->len && !FL_TEST(str, STR_ASSOC)) { str2 = rb_str_new4(str); str2 = str_new3(rb_obj_class(str2), str2); RSTRING(str2)->ptr += RSTRING(str2)->len - len; RSTRING(str2)->len = len; } else { str2 = rb_str_new5(str, RSTRING(str)->ptr+beg, len); } OBJ_INFECT(str2, str); return str2; } VALUE rb_str_freeze(str) VALUE str; { return rb_obj_freeze(str); } VALUE rb_str_dup_frozen(str) VALUE str; { if (FL_TEST(str, ELTS_SHARED) && RSTRING(str)->aux.shared) { VALUE shared = RSTRING(str)->aux.shared; if (RSTRING(shared)->len == RSTRING(str)->len) { 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(str) 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(str) VALUE str; { if (!FL_TEST(str, STR_TMPLOCK)) { rb_raise(rb_eRuntimeError, "temporal unlocking already unlocked string"); } FL_UNSET(str, STR_TMPLOCK); return str; } VALUE rb_str_resize(str, len) VALUE str; long len; { if (len < 0) { rb_raise(rb_eArgError, "negative string size (or size too big)"); } rb_str_modify(str); if (len != RSTRING(str)->len) { if (RSTRING(str)->len < len || RSTRING(str)->len - len > 1024) { REALLOC_N(RSTRING(str)->ptr, char, len+1); if (!FL_TEST(str, STR_NOCAPA)) { RSTRING(str)->aux.capa = len; } } RSTRING(str)->len = len; RSTRING(str)->ptr[len] = '\0'; /* sentinel */ } return str; } static VALUE str_buf_cat(str, ptr, len) VALUE str; const char *ptr; long len; { long capa, total, off = -1;; rb_str_modify(str); if (ptr >= RSTRING(str)->ptr && ptr <= RSTRING(str)->ptr + RSTRING(str)->len) { off = ptr - RSTRING(str)->ptr; } if (len == 0) return 0; if (FL_TEST(str, STR_ASSOC)) { FL_UNSET(str, STR_ASSOC); capa = RSTRING(str)->aux.capa = RSTRING(str)->len; } else { capa = RSTRING(str)->aux.capa; } if (RSTRING(str)->len >= LONG_MAX - len) { rb_raise(rb_eArgError, "string sizes too big"); } total = RSTRING(str)->len+len; if (capa <= total) { while (total > capa) { if (capa + 1 >= LONG_MAX / 2) { capa = total; break; } capa = (capa + 1) * 2; } RESIZE_CAPA(str, capa); } if (off != -1) { ptr = RSTRING(str)->ptr + off; } memcpy(RSTRING(str)->ptr + RSTRING(str)->len, ptr, len); RSTRING(str)->len = total; RSTRING(str)->ptr[total] = '\0'; /* sentinel */ return str; } VALUE rb_str_buf_cat(str, ptr, len) VALUE str; const char *ptr; long len; { if (len == 0) return str; if (len < 0) { rb_raise(rb_eArgError, "negative string size (or size too big)"); } return str_buf_cat(str, ptr, len); } VALUE rb_str_buf_cat2(str, ptr) VALUE str; const char *ptr; { return rb_str_buf_cat(str, ptr, strlen(ptr)); } VALUE rb_str_cat(str, ptr, len) VALUE str; const char *ptr; long len; { if (len < 0) { rb_raise(rb_eArgError, "negative string size (or size too big)"); } if (FL_TEST(str, STR_ASSOC)) { rb_str_modify(str); REALLOC_N(RSTRING(str)->ptr, char, RSTRING(str)->len+len+1); memcpy(RSTRING(str)->ptr + RSTRING(str)->len, ptr, len); RSTRING(str)->len += len; RSTRING(str)->ptr[RSTRING(str)->len] = '\0'; /* sentinel */ return str; } return rb_str_buf_cat(str, ptr, len); } VALUE rb_str_cat2(str, ptr) VALUE str; const char *ptr; { return rb_str_cat(str, ptr, strlen(ptr)); } VALUE rb_str_buf_append(str, str2) VALUE str, str2; { str_buf_cat(str, RSTRING(str2)->ptr, RSTRING(str2)->len); OBJ_INFECT(str, str2); return str; } VALUE rb_str_append(str, str2) VALUE str, str2; { StringValue(str2); rb_str_modify(str); if (RSTRING(str2)->len > 0) { if (FL_TEST(str, STR_ASSOC)) { long len = RSTRING(str)->len+RSTRING(str2)->len; REALLOC_N(RSTRING(str)->ptr, char, len+1); memcpy(RSTRING(str)->ptr + RSTRING(str)->len, RSTRING(str2)->ptr, RSTRING(str2)->len); RSTRING(str)->ptr[len] = '\0'; /* sentinel */ RSTRING(str)->len = len; } else { return rb_str_buf_append(str, str2); } } OBJ_INFECT(str, str2); return str; } /* * 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 between 0 and 255, it is converted to a character before * concatenation. * * a = "hello " * a << "world" #=> "hello world" * a.concat(33) #=> "hello world!" */ VALUE rb_str_concat(str1, str2) VALUE str1, str2; { if (FIXNUM_P(str2)) { int i = FIX2INT(str2); if (0 <= i && i <= 0xff) { /* byte */ char c = i; return rb_str_cat(str1, &c, 1); } } str1 = rb_str_append(str1, str2); return str1; } int rb_str_hash(str) VALUE str; { register long len = RSTRING(str)->len; register char *p = RSTRING(str)->ptr; register int key = 0; #if defined(HASH_ELFHASH) register unsigned int g; while (len--) { key = (key << 4) + *p++; if (g = key & 0xF0000000) key ^= g >> 24; key &= ~g; } #elif defined(HASH_PERL) while (len--) { key += *p++; key += (key << 10); key ^= (key >> 6); } key += (key << 3); key ^= (key >> 11); key += (key << 15); #else while (len--) { key = key*65599 + *p; p++; } key = key + (key>>5); #endif return key; } /* * call-seq: * str.hash => fixnum * * Return a hash based on the string's length and content. */ static VALUE rb_str_hash_m(str) VALUE str; { int key = rb_str_hash(str); return INT2FIX(key); } #define lesser(a,b) (((a)>(b))?(b):(a)) int rb_str_cmp(str1, str2) VALUE str1, str2; { long len; int retval; len = lesser(RSTRING(str1)->len, RSTRING(str2)->len); retval = rb_memcmp(RSTRING(str1)->ptr, RSTRING(str2)->ptr, len); if (retval == 0) { if (RSTRING(str1)->len == RSTRING(str2)->len) return 0; if (RSTRING(str1)->len > RSTRING(str2)->len) 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. */ static VALUE rb_str_equal(str1, str2) VALUE str1, str2; { 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 (RSTRING(str1)->len == RSTRING(str2)->len && rb_str_cmp(str1, str2) == 0) { return Qtrue; } return Qfalse; } #define IS_EVSTR(p,e) ((p) < (e) && (*(p) == '$' || *(p) == '@' || *(p) == '{')) /* * 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(str1, str2) VALUE str1, str2; { if (TYPE(str2) != T_STRING || RSTRING(str1)->len != RSTRING(str2)->len) return Qfalse; if (memcmp(RSTRING(str1)->ptr, RSTRING(str2)->ptr, lesser(RSTRING(str1)->len, RSTRING(str2)->len)) == 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. If the variable $= is * false, the comparison is based on comparing the binary values * of each character in the string. 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(str1, str2) VALUE str1, 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(str1, str2) VALUE str1, str2; { long len; int retval; StringValue(str2); len = lesser(RSTRING(str1)->len, RSTRING(str2)->len); retval = rb_memcicmp(RSTRING(str1)->ptr, RSTRING(str2)->ptr, len); if (retval == 0) { if (RSTRING(str1)->len == RSTRING(str2)->len) return INT2FIX(0); if (RSTRING(str1)->len > RSTRING(str2)->len) return INT2FIX(1); return INT2FIX(-1); } if (retval == 0) return INT2FIX(0); if (retval > 0) return INT2FIX(1); return INT2FIX(-1); } static long rb_str_index(str, sub, offset) VALUE str, sub; long offset; { long pos; if (offset < 0) { offset += RSTRING(str)->len; if (offset < 0) return -1; } if (RSTRING(str)->len - offset < RSTRING(sub)->len) return -1; if (RSTRING(sub)->len == 0) return offset; pos = rb_memsearch(RSTRING(sub)->ptr, RSTRING(sub)->len, RSTRING(str)->ptr+offset, RSTRING(str)->len-offset); if (pos < 0) return pos; 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(argc, argv, str) 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 += RSTRING(str)->len; 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); break; case T_FIXNUM: { int c = FIX2INT(sub); long len = RSTRING(str)->len; unsigned char *p = (unsigned char*)RSTRING(str)->ptr; for (;poslen; char *s, *sbeg, *t; /* substring longer than string */ if (RSTRING(str)->len < len) return -1; if (RSTRING(str)->len - pos < len) { pos = RSTRING(str)->len - len; } sbeg = RSTRING(str)->ptr; s = RSTRING(str)->ptr + pos; t = RSTRING(sub)->ptr; if (len) { while (sbeg <= s) { if (rb_memcmp(s, t, len) == 0) { return s - RSTRING(str)->ptr; } s--; } return -1; } else { return pos; } } /* * 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(argc, argv, str) int argc; VALUE *argv; VALUE str; { VALUE sub; VALUE position; long pos; if (rb_scan_args(argc, argv, "11", &sub, &position) == 2) { pos = NUM2LONG(position); if (pos < 0) { pos += RSTRING(str)->len; if (pos < 0) { if (TYPE(sub) == T_REGEXP) { rb_backref_set(Qnil); } return Qnil; } } if (pos > RSTRING(str)->len) pos = RSTRING(str)->len; } else { pos = RSTRING(str)->len; } switch (TYPE(sub)) { case T_REGEXP: if (RREGEXP(sub)->len) { pos = rb_reg_adjust_startpos(sub, str, pos, 1); pos = rb_reg_search(sub, str, pos, 1); } if (pos >= 0) return LONG2NUM(pos); break; case T_STRING: pos = rb_str_rindex(str, sub, pos); if (pos >= 0) return LONG2NUM(pos); break; case T_FIXNUM: { int c = FIX2INT(sub); unsigned char *p = (unsigned char*)RSTRING(str)->ptr + pos; unsigned char *pbeg = (unsigned char*)RSTRING(str)->ptr; if (pos == RSTRING(str)->len) { if (pos == 0) return Qnil; --p; } while (pbeg <= p) { if (*p == c) return LONG2NUM((char*)p - RSTRING(str)->ptr); p--; } return Qnil; } default: rb_raise(rb_eTypeError, "type mismatch: %s given", rb_obj_classname(sub)); } 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(x, y) VALUE x, 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. * * 'hello'.match('(.)\1') #=> # * 'hello'.match('(.)\1')[0] #=> "ll" * 'hello'.match(/(.)\1/)[0] #=> "ll" * 'hello'.match('xx') #=> nil */ static VALUE rb_str_match_m(str, re) VALUE str, re; { return rb_funcall(get_pat(re, 0), rb_intern("match"), 1, str); } static char succ_char(s) char *s; { char c = *s; /* numerics */ if ('0' <= c && c < '9') (*s)++; else if (c == '9') { *s = '0'; return '1'; } /* small alphabets */ else if ('a' <= c && c < 'z') (*s)++; else if (c == 'z') { return *s = 'a'; } /* capital alphabets */ else if ('A' <= c && c < 'Z') (*s)++; else if (c == 'Z') { return *s = 'A'; } return 0; } /* * 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 #=> "**+" */ static VALUE rb_str_succ(orig) VALUE orig; { VALUE str; char *sbeg, *s; int c = -1; long n = 0; str = rb_str_new5(orig, RSTRING(orig)->ptr, RSTRING(orig)->len); OBJ_INFECT(str, orig); if (RSTRING(str)->len == 0) return str; sbeg = RSTRING(str)->ptr; s = sbeg + RSTRING(str)->len - 1; while (sbeg <= s) { if (ISALNUM(*s)) { if ((c = succ_char(s)) == 0) break; n = s - sbeg; } s--; } if (c == -1) { /* str contains no alnum */ sbeg = RSTRING(str)->ptr; s = sbeg + RSTRING(str)->len - 1; c = '\001'; while (sbeg <= s) { if ((*s += 1) != 0) break; s--; } } if (s < sbeg) { RESIZE_CAPA(str, RSTRING(str)->len + 1); s = RSTRING(str)->ptr + n; memmove(s+1, s, RSTRING(str)->len - n); *s = c; RSTRING(str)->len += 1; RSTRING(str)->ptr[RSTRING(str)->len] = '\0'; } 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(str) VALUE str; { rb_str_shared_replace(str, rb_str_succ(str)); return str; } VALUE rb_str_upto(beg, end, excl) VALUE beg, end; int excl; { VALUE current, after_end; ID succ = rb_intern("succ"); int n; StringValue(end); 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; StringValue(current); if (RSTRING(current)->len > RSTRING(end)->len || RSTRING(current)->len == 0) break; } return beg; } /* * call-seq: * str.upto(other_str) {|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. * * "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_m(beg, end) VALUE beg, end; { return rb_str_upto(beg, end, Qfalse); } static VALUE rb_str_subpat(str, re, nth) VALUE str, 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(str, indx) VALUE str; VALUE indx; { long idx; switch (TYPE(indx)) { case T_FIXNUM: idx = FIX2LONG(indx); num_index: if (idx < 0) { idx = RSTRING(str)->len + idx; } if (idx < 0 || RSTRING(str)->len <= idx) { return Qnil; } return INT2FIX(RSTRING(str)->ptr[idx] & 0xff); 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; switch (rb_range_beg_len(indx, &beg, &len, RSTRING(str)->len, 0)) { case Qfalse: break; case Qnil: return Qnil; default: tmp = rb_str_substr(str, beg, len); OBJ_INFECT(tmp, indx); return tmp; } } idx = NUM2LONG(indx); goto num_index; } return Qnil; /* not reached */ } /* * call-seq: * str[fixnum] => fixnum 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) => 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(regexp, fixnum) => new_str or nil * str.slice(other_str) => new_str or nil * * Element Reference---If passed a single Fixnum, returns the code * of the 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] #=> 101 * 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(argc, argv, str) 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(str, beg, len, val) VALUE str; long beg, len; VALUE val; { if (len < 0) rb_raise(rb_eIndexError, "negative length %ld", len); StringValue(val); rb_str_modify(str); if (RSTRING(str)->len < beg) { out_of_range: rb_raise(rb_eIndexError, "index %ld out of string", beg); } if (beg < 0) { if (-beg > RSTRING(str)->len) { goto out_of_range; } beg += RSTRING(str)->len; } if (RSTRING(str)->len < len || RSTRING(str)->len < beg + len) { len = RSTRING(str)->len - beg; } if (len < RSTRING(val)->len) { /* expand string */ RESIZE_CAPA(str, RSTRING(str)->len + RSTRING(val)->len - len + 1); } if (RSTRING(val)->len != len) { memmove(RSTRING(str)->ptr + beg + RSTRING(val)->len, RSTRING(str)->ptr + beg + len, RSTRING(str)->len - (beg + len)); } if (RSTRING(str)->len < beg && len < 0) { MEMZERO(RSTRING(str)->ptr + RSTRING(str)->len, char, -len); } if (RSTRING(val)->len > 0) { memmove(RSTRING(str)->ptr+beg, RSTRING(val)->ptr, RSTRING(val)->len); } RSTRING(str)->len += RSTRING(val)->len - len; if (RSTRING(str)->ptr) { RSTRING(str)->ptr[RSTRING(str)->len] = '\0'; } OBJ_INFECT(str, val); } void rb_str_update(str, beg, len, val) VALUE str; long beg, len; VALUE val; { rb_str_splice(str, beg, len, val); } static void rb_str_subpat_set(str, re, nth, val) VALUE str, re; int nth; VALUE val; { VALUE match; long start, end, len; if (rb_reg_search(re, str, 0, 0) < 0) { rb_raise(rb_eIndexError, "regexp not matched"); } match = rb_backref_get(); if (nth >= RMATCH(match)->regs->num_regs) { out_of_range: rb_raise(rb_eIndexError, "index %d out of regexp", nth); } if (nth < 0) { if (-nth >= RMATCH(match)->regs->num_regs) { goto out_of_range; } nth += RMATCH(match)->regs->num_regs; } start = RMATCH(match)->BEG(nth); if (start == -1) { rb_raise(rb_eIndexError, "regexp group %d not matched", nth); } end = RMATCH(match)->END(nth); len = end - start; rb_str_splice(str, start, len, val); } static VALUE rb_str_aset(str, indx, val) VALUE str; VALUE indx, val; { long idx, beg; switch (TYPE(indx)) { case T_FIXNUM: idx = FIX2LONG(indx); num_index: if (RSTRING(str)->len <= idx) { out_of_range: rb_raise(rb_eIndexError, "index %ld out of string", idx); } if (idx < 0) { if (-idx > RSTRING(str)->len) goto out_of_range; idx += RSTRING(str)->len; } if (FIXNUM_P(val)) { rb_str_modify(str); if (RSTRING(str)->len == idx) { RSTRING(str)->len += 1; RESIZE_CAPA(str, RSTRING(str)->len); } RSTRING(str)->ptr[idx] = FIX2INT(val) & 0xff; } else { 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"); } rb_str_splice(str, beg, RSTRING(indx)->len, val); return val; default: /* check if indx is Range */ { long beg, len; if (rb_range_beg_len(indx, &beg, &len, RSTRING(str)->len, 2)) { rb_str_splice(str, beg, len, val); return val; } } idx = NUM2LONG(indx); goto num_index; } } /* * call-seq: * str[fixnum] = fixnum * 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(argc, argv, str) 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(str, idx, str2) VALUE str, idx, str2; { long pos = NUM2LONG(idx); if (pos == -1) { pos = RSTRING(str)->len; } 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. 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. * * 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(argc, argv, str) 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(argc, argv, str) int argc; VALUE *argv; VALUE str; { VALUE pat, repl, match; 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]; 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) { match = rb_backref_get(); regs = RMATCH(match)->regs; if (iter) { char *p = RSTRING(str)->ptr; long len = RSTRING(str)->len; rb_match_busy(match); repl = rb_obj_as_string(rb_yield(rb_reg_nth_match(0, match))); str_mod_check(str, p, len); str_frozen_check(str); rb_backref_set(match); } else { repl = rb_reg_regsub(repl, str, regs); } rb_str_modify(str); if (OBJ_TAINTED(repl)) tainted = 1; plen = END(0) - BEG(0); if (RSTRING(repl)->len > plen) { RESIZE_CAPA(str, RSTRING(str)->len + RSTRING(repl)->len - plen); } if (RSTRING(repl)->len != plen) { memmove(RSTRING(str)->ptr + BEG(0) + RSTRING(repl)->len, RSTRING(str)->ptr + BEG(0) + plen, RSTRING(str)->len - BEG(0) - plen); } memcpy(RSTRING(str)->ptr + BEG(0), RSTRING(repl)->ptr, RSTRING(repl)->len); RSTRING(str)->len += RSTRING(repl)->len - plen; RSTRING(str)->ptr[RSTRING(str)->len] = '\0'; 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, 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" */ static VALUE rb_str_sub(argc, argv, str) int argc; VALUE *argv; VALUE str; { str = rb_str_dup(str); rb_str_sub_bang(argc, argv, str); return str; } static VALUE str_gsub(argc, argv, str, bang) int argc; VALUE *argv; VALUE str; int bang; { VALUE pat, val, repl, match, dest; struct re_registers *regs; long beg, n; long offset, blen, slen, len; int iter = 0; char *buf, *bp, *sp, *cp; int tainted = 0; if (argc == 1 && rb_block_given_p()) { iter = 1; } else if (argc == 2) { repl = argv[1]; 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); 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(str)->len + 30; /* len + margin */ dest = str_new(0, 0, blen); buf = RSTRING(dest)->ptr; bp = buf; sp = cp = RSTRING(str)->ptr; slen = RSTRING(str)->len; rb_str_locktmp(dest); while (beg >= 0) { n++; match = rb_backref_get(); regs = RMATCH(match)->regs; if (iter) { rb_match_busy(match); val = rb_obj_as_string(rb_yield(rb_reg_nth_match(0, match))); str_mod_check(str, sp, slen); if (bang) str_frozen_check(str); if (val == dest) { /* paranoid chack [ruby-dev:24827] */ rb_raise(rb_eRuntimeError, "block should not cheat"); } rb_backref_set(match); } else { val = rb_reg_regsub(repl, str, regs); } if (OBJ_TAINTED(val)) tainted = 1; len = (bp - buf) + (beg - offset) + RSTRING(val)->len + 3; if (blen < len) { while (blen < len) blen *= 2; len = bp - buf; RESIZE_CAPA(dest, blen); RSTRING(dest)->len = blen; buf = RSTRING(dest)->ptr; bp = buf + len; } len = beg - offset; /* copy pre-match substr */ memcpy(bp, cp, len); bp += len; memcpy(bp, RSTRING(val)->ptr, RSTRING(val)->len); bp += RSTRING(val)->len; 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(str)->len <= END(0)) break; len = mbclen2(RSTRING(str)->ptr[END(0)], pat); memcpy(bp, RSTRING(str)->ptr+END(0), len); bp += len; offset = END(0) + len; } cp = RSTRING(str)->ptr + offset; if (offset > RSTRING(str)->len) break; beg = rb_reg_search(pat, str, offset, 0); } if (RSTRING(str)->len > offset) { len = bp - buf; if (blen - len < RSTRING(str)->len - offset) { blen = len + RSTRING(str)->len - offset; RESIZE_CAPA(dest, blen); buf = RSTRING(dest)->ptr; bp = buf + len; } memcpy(bp, cp, RSTRING(str)->len - offset); bp += RSTRING(str)->len - offset; } rb_backref_set(match); *bp = '\0'; rb_str_unlocktmp(dest); if (bang) { if (str_independent(str)) { free(RSTRING(str)->ptr); } FL_UNSET(str, STR_NOCAPA); RSTRING(str)->ptr = buf; RSTRING(str)->aux.capa = blen; RSTRING(dest)->ptr = 0; RSTRING(dest)->len = 0; } else { RBASIC(dest)->klass = rb_obj_class(str); OBJ_INFECT(dest, str); str = dest; } RSTRING(str)->len = bp - buf; 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(argc, argv, str) 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, 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 " */ static VALUE rb_str_gsub(argc, argv, str) 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(str, str2) VALUE str, str2; { if (str == str2) return str; StringValue(str2); if (FL_TEST(str2, ELTS_SHARED)) { if (str_independent(str)) { free(RSTRING(str)->ptr); } RSTRING(str)->len = RSTRING(str2)->len; RSTRING(str)->ptr = RSTRING(str2)->ptr; FL_SET(str, ELTS_SHARED); FL_UNSET(str, STR_ASSOC); RSTRING(str)->aux.shared = RSTRING(str2)->aux.shared; } else { if (str_independent(str)) { rb_str_resize(str, RSTRING(str2)->len); memcpy(RSTRING(str)->ptr, RSTRING(str2)->ptr, RSTRING(str2)->len); if (!RSTRING(str)->ptr) { make_null_str(str); } } else { RSTRING(str)->ptr = RSTRING(str2)->ptr; RSTRING(str)->len = RSTRING(str2)->len; str_make_independent(str); } if (FL_TEST(str2, STR_ASSOC)) { FL_SET(str, STR_ASSOC); RSTRING(str)->aux.shared = RSTRING(str2)->aux.shared; } } OBJ_INFECT(str, str2); return str; } static VALUE uscore_get() { VALUE line; line = rb_lastline_get(); if (TYPE(line) != T_STRING) { rb_raise(rb_eTypeError, "$_ value need to be String (%s given)", NIL_P(line) ? "nil" : rb_obj_classname(line)); } return line; } /* * call-seq: * sub!(pattern, replacement) => $_ or nil * sub!(pattern) {|...| block } => $_ or nil * * Equivalent to $_.sub!(args). */ static VALUE rb_f_sub_bang(argc, argv) int argc; VALUE *argv; { return rb_str_sub_bang(argc, argv, uscore_get()); } /* * call-seq: * sub(pattern, replacement) => $_ * sub(pattern) { block } => $_ * * Equivalent to $_.sub(args), except that * $_ will be updated if substitution occurs. */ static VALUE rb_f_sub(argc, argv) int argc; VALUE *argv; { VALUE str = rb_str_dup(uscore_get()); if (NIL_P(rb_str_sub_bang(argc, argv, str))) return str; rb_lastline_set(str); return str; } /* * call-seq: * gsub!(pattern, replacement) => string or nil * gsub!(pattern) {|...| block } => string or nil * * Equivalent to Kernel::gsub, except nil is * returned if $_ is not modified. * * $_ = "quick brown fox" * gsub! /cat/, '*' #=> nil * $_ #=> "quick brown fox" */ static VALUE rb_f_gsub_bang(argc, argv) int argc; VALUE *argv; { return rb_str_gsub_bang(argc, argv, uscore_get()); } /* * call-seq: * gsub(pattern, replacement) => string * gsub(pattern) {|...| block } => string * * Equivalent to $_.gsub..., except that $_ * receives the modified result. * * $_ = "quick brown fox" * gsub /[aeiou]/, '*' #=> "q**ck br*wn f*x" * $_ #=> "q**ck br*wn f*x" */ static VALUE rb_f_gsub(argc, argv) int argc; VALUE *argv; { VALUE str = rb_str_dup(uscore_get()); if (NIL_P(rb_str_gsub_bang(argc, argv, str))) return str; rb_lastline_set(str); return str; } /* * call-seq: * str.reverse! => str * * Reverses str in place. */ static VALUE rb_str_reverse_bang(str) VALUE str; { char *s, *e; char c; if (RSTRING(str)->len > 1) { rb_str_modify(str); s = RSTRING(str)->ptr; e = s + RSTRING(str)->len - 1; while (s < e) { c = *s; *s++ = *e; *e-- = c; } } return str; } /* * 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(str) VALUE str; { VALUE obj; char *s, *e, *p; if (RSTRING(str)->len <= 1) return rb_str_dup(str); obj = rb_str_new5(str, 0, RSTRING(str)->len); s = RSTRING(str)->ptr; e = s + RSTRING(str)->len - 1; p = RSTRING(obj)->ptr; while (e >= s) { *p++ = *e--; } OBJ_INFECT(obj, str); return obj; } /* * 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(str, arg) VALUE str, arg; { long i; if (FIXNUM_P(arg)) { if (memchr(RSTRING(str)->ptr, FIX2INT(arg), RSTRING(str)->len)) return Qtrue; return Qfalse; } 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 (2, 8, 10, or 16). 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(argc, argv, str) 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, "illegal 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(str) VALUE str; { return rb_float_new(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(str) VALUE str; { if (rb_obj_class(str) != rb_cString) { VALUE dup = str_alloc(rb_cString); rb_str_replace(dup, str); return dup; } return str; } /* * call-seq: * str.inspect => string * * Returns a printable version of _str_, with special characters * escaped. * * str = "hello" * str[3] = 8 * str.inspect #=> "hel\010o" */ VALUE rb_str_inspect(str) VALUE str; { char *p, *pend; VALUE result = rb_str_buf_new2("\""); char s[5]; p = RSTRING(str)->ptr; pend = p + RSTRING(str)->len; while (p < pend) { char c = *p++; if (ismbchar(c) && p < pend) { int len = mbclen(c); rb_str_buf_cat(result, p - 1, len); p += len - 1; } else if (c == '"'|| c == '\\' || (c == '#' && IS_EVSTR(p, pend))) { s[0] = '\\'; s[1] = c; rb_str_buf_cat(result, s, 2); } else if (ISPRINT(c)) { s[0] = c; rb_str_buf_cat(result, s, 1); } else if (c == '\n') { s[0] = '\\'; s[1] = 'n'; rb_str_buf_cat(result, s, 2); } else if (c == '\r') { s[0] = '\\'; s[1] = 'r'; rb_str_buf_cat(result, s, 2); } else if (c == '\t') { s[0] = '\\'; s[1] = 't'; rb_str_buf_cat(result, s, 2); } else if (c == '\f') { s[0] = '\\'; s[1] = 'f'; rb_str_buf_cat(result, s, 2); } else if (c == '\013') { s[0] = '\\'; s[1] = 'v'; rb_str_buf_cat(result, s, 2); } else if (c == '\010') { s[0] = '\\'; s[1] = 'b'; rb_str_buf_cat(result, s, 2); } else if (c == '\007') { s[0] = '\\'; s[1] = 'a'; rb_str_buf_cat(result, s, 2); } else if (c == 033) { s[0] = '\\'; s[1] = 'e'; rb_str_buf_cat(result, s, 2); } else { sprintf(s, "\\%03o", c & 0377); rb_str_buf_cat2(result, s); } } rb_str_buf_cat2(result, "\""); OBJ_INFECT(result, str); return result; } /* * 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(str) VALUE str; { long len; char *p, *pend; char *q, *qend; VALUE result; len = 2; /* "" */ p = RSTRING(str)->ptr; pend = p + RSTRING(str)->len; while (p < pend) { 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; /* \nnn */ } break; } } result = rb_str_new5(str, 0, len); p = RSTRING(str)->ptr; pend = p + RSTRING(str)->len; q = RSTRING(result)->ptr; qend = q + len; *q++ = '"'; while (p < pend) { char c = *p++; if (c == '"' || c == '\\') { *q++ = '\\'; *q++ = c; } else if (c == '#') { if (IS_EVSTR(p, pend)) *q++ = '\\'; *q++ = '#'; } else if (ISPRINT(c)) { *q++ = c; } 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 { *q++ = '\\'; sprintf(q, "%03o", c&0xff); q += 3; } } *q++ = '"'; OBJ_INFECT(result, str); return result; } /* * call-seq: * str.upcase! => str or nil * * Upcases the contents of str, returning nil if no changes * were made. */ static VALUE rb_str_upcase_bang(str) VALUE str; { char *s, *send; int modify = 0; rb_str_modify(str); s = RSTRING(str)->ptr; send = s + RSTRING(str)->len; while (s < send) { if (ismbchar(*s)) { s+=mbclen(*s) - 1; } else if (ISLOWER(*s)) { *s = toupper(*s); modify = 1; } s++; } 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. * * "hEllO".upcase #=> "HELLO" */ static VALUE rb_str_upcase(str) 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. */ static VALUE rb_str_downcase_bang(str) VALUE str; { char *s, *send; int modify = 0; rb_str_modify(str); s = RSTRING(str)->ptr; send = s + RSTRING(str)->len; while (s < send) { if (ismbchar(*s)) { s+=mbclen(*s) - 1; } else if (ISUPPER(*s)) { *s = tolower(*s); modify = 1; } s++; } 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. * * "hEllO".downcase #=> "hello" */ static VALUE rb_str_downcase(str) 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. * * a = "hello" * a.capitalize! #=> "Hello" * a #=> "Hello" * a.capitalize! #=> nil */ static VALUE rb_str_capitalize_bang(str) VALUE str; { char *s, *send; int modify = 0; rb_str_modify(str); if (RSTRING(str)->len == 0 || !RSTRING(str)->ptr) return Qnil; s = RSTRING(str)->ptr; send = s + RSTRING(str)->len; if (ISLOWER(*s)) { *s = toupper(*s); modify = 1; } while (++s < send) { if (ismbchar(*s)) { s+=mbclen(*s) - 1; } else if (ISUPPER(*s)) { *s = tolower(*s); modify = 1; } } 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. * * "hello".capitalize #=> "Hello" * "HELLO".capitalize #=> "Hello" * "123ABC".capitalize #=> "123abc" */ static VALUE rb_str_capitalize(str) 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. */ static VALUE rb_str_swapcase_bang(str) VALUE str; { char *s, *send; int modify = 0; rb_str_modify(str); s = RSTRING(str)->ptr; send = s + RSTRING(str)->len; while (s < send) { if (ismbchar(*s)) { s+=mbclen(*s) - 1; } else if (ISUPPER(*s)) { *s = tolower(*s); modify = 1; } else if (ISLOWER(*s)) { *s = toupper(*s); modify = 1; } s++; } 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. * * "Hello".swapcase #=> "hELLO" * "cYbEr_PuNk11".swapcase #=> "CyBeR_pUnK11" */ static VALUE rb_str_swapcase(str) 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(t) struct tr *t; { for (;;) { if (!t->gen) { if (t->p == t->pend) return -1; if (t->p < t->pend - 1 && *t->p == '\\') { t->p++; } t->now = *(USTR)t->p++; if (t->p < t->pend - 1 && *t->p == '-') { t->p++; if (t->p < t->pend) { if (t->now > *(USTR)t->p) { t->p++; continue; } t->gen = 1; t->max = *(USTR)t->p++; } } 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(str, src, repl, sflag) VALUE str, src, repl; int sflag; { struct tr trsrc, trrepl; int cflag = 0; int trans[256]; int i, c, modify = 0; char *s, *send; StringValue(src); StringValue(repl); if (RSTRING(str)->len == 0 || !RSTRING(str)->ptr) return Qnil; trsrc.p = RSTRING(src)->ptr; trsrc.pend = trsrc.p + RSTRING(src)->len; if (RSTRING(src)->len >= 2 && RSTRING(src)->ptr[0] == '^') { cflag++; trsrc.p++; } if (RSTRING(repl)->len == 0) { return rb_str_delete_bang(1, &src, str); } trrepl.p = RSTRING(repl)->ptr; trrepl.pend = trrepl.p + RSTRING(repl)->len; 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)) >= 0) { trans[c & 0xff] = -1; } while ((c = trnext(&trrepl)) >= 0) /* retrieve last replacer */; for (i=0; i<256; i++) { if (trans[i] >= 0) { trans[i] = trrepl.now; } } } else { int r; for (i=0; i<256; i++) { trans[i] = -1; } while ((c = trnext(&trsrc)) >= 0) { r = trnext(&trrepl); if (r == -1) r = trrepl.now; trans[c & 0xff] = r; } } rb_str_modify(str); s = RSTRING(str)->ptr; send = s + RSTRING(str)->len; if (sflag) { char *t = s; int c0, last = -1; while (s < send) { c0 = *s++; if ((c = trans[c0 & 0xff]) >= 0) { if (last == c) continue; last = c; *t++ = c & 0xff; modify = 1; } else { last = -1; *t++ = c0; } } if (RSTRING(str)->len > (t - RSTRING(str)->ptr)) { RSTRING(str)->len = (t - RSTRING(str)->ptr); modify = 1; *t = '\0'; } } else { while (s < send) { if ((c = trans[*s & 0xff]) >= 0) { *s = c & 0xff; modify = 1; } s++; } } if (modify) 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(str, src, repl) VALUE str, src, 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(str, src, repl) VALUE str, src, repl; { str = rb_str_dup(str); tr_trans(str, src, repl, 0); return str; } static void tr_setup_table(str, table, init) VALUE str; char table[256]; int init; { char buf[256]; struct tr tr; int i, c; int cflag = 0; tr.p = RSTRING(str)->ptr; tr.pend = tr.p + RSTRING(str)->len; tr.gen = tr.now = tr.max = 0; if (RSTRING(str)->len > 1 && RSTRING(str)->ptr[0] == '^') { cflag = 1; tr.p++; } if (init) { for (i=0; i<256; i++) { table[i] = 1; } } for (i=0; i<256; i++) { buf[i] = cflag; } while ((c = trnext(&tr)) >= 0) { buf[c & 0xff] = !cflag; } for (i=0; i<256; i++) { table[i] = table[i] && buf[i]; } } /* * 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(argc, argv, str) int argc; VALUE *argv; VALUE str; { char *s, *send, *t; char squeez[256]; int modify = 0; int init = 1; int i; if (argc < 1) { rb_raise(rb_eArgError, "wrong number of arguments"); } for (i=0; iptr; if (!s || RSTRING(str)->len == 0) return Qnil; send = s + RSTRING(str)->len; while (s < send) { if (squeez[*s & 0xff]) modify = 1; else *t++ = *s; s++; } *t = '\0'; RSTRING(str)->len = t - RSTRING(str)->ptr; if (modify) return str; return Qnil; } /* * call-seq: * str.delete([other_str]+) => 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(argc, argv, str) 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(argc, argv, str) int argc; VALUE *argv; VALUE str; { char squeez[256]; char *s, *send, *t; int c, save, modify = 0; int init = 1; int i; if (argc == 0) { for (i=0; i<256; i++) { squeez[i] = 1; } } else { for (i=0; iptr; if (!s || RSTRING(str)->len == 0) return Qnil; send = s + RSTRING(str)->len; save = -1; while (s < send) { c = *s++ & 0xff; if (c != save || !squeez[c]) { *t++ = save = c; } } *t = '\0'; if (t - RSTRING(str)->ptr != RSTRING(str)->len) { RSTRING(str)->len = t - RSTRING(str)->ptr; 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(argc, argv, str) 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(str, src, repl) VALUE str, src, 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(str, src, repl) VALUE str, src, 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(argc, argv, str) int argc; VALUE *argv; VALUE str; { char table[256]; char *s, *send; int init = 1; int i; if (argc < 1) { rb_raise(rb_eArgError, "wrong number of arguments"); } for (i=0; iptr; if (!s || RSTRING(str)->len == 0) return INT2FIX(0); send = s + RSTRING(str)->len; i = 0; while (s < send) { if (table[*s++ & 0xff]) { i++; } } return INT2NUM(i); } /* * call-seq: * str.split(pattern=$;, [limit]) => 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 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(argc, argv, str) int argc; VALUE *argv; VALUE str; { 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(str)->len == 0) return rb_ary_new2(0); return rb_ary_new3(1, str); } i = 1; } 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(spat)->len == 1) { if (RSTRING(spat)->ptr[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(str)->ptr; long len = RSTRING(str)->len; char *eptr = ptr + len; int skip = 1; for (end = beg = 0; ptr= 0) { regs = RMATCH(rb_backref_get())->regs; if (start == end && BEG(0) == END(0)) { if (!RSTRING(str)->ptr) { rb_ary_push(result, rb_str_new("", 0)); break; } else if (last_null == 1) { rb_ary_push(result, rb_str_substr(str, beg, mbclen2(RSTRING(str)->ptr[beg],spat))); beg = start; } else { start += mbclen2(RSTRING(str)->ptr[start],spat); last_null = 1; continue; } } else { rb_ary_push(result, rb_str_substr(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_substr(str, BEG(idx), END(idx)-BEG(idx)); rb_ary_push(result, tmp); } if (!NIL_P(limit) && lim <= ++i) break; } } if (RSTRING(str)->len > 0 && (!NIL_P(limit) || RSTRING(str)->len > beg || lim < 0)) { if (RSTRING(str)->len == beg) tmp = rb_str_new5(str, 0, 0); else tmp = rb_str_substr(str, beg, RSTRING(str)->len-beg); rb_ary_push(result, tmp); } if (NIL_P(limit) && lim == 0) { while (RARRAY(result)->len > 0 && RSTRING(RARRAY(result)->ptr[RARRAY(result)->len-1])->len == 0) rb_ary_pop(result); } return result; } VALUE rb_str_split(str, sep0) VALUE str; const char *sep0; { VALUE sep; StringValue(str); sep = rb_str_new2(sep0); return rb_str_split_m(1, &sep, str); } /* * call-seq: * split([pattern [, limit]]) => array * * Equivalent to $_.split(pattern, limit). * See String#split. */ static VALUE rb_f_split(argc, argv) int argc; VALUE *argv; { return rb_str_split_m(argc, argv, uscore_get()); } /* * call-seq: * str.each(separator=$/) {|substr| block } => str * 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(argc, argv, str) int argc; VALUE *argv; VALUE str; { VALUE rs; int newline; char *p = RSTRING(str)->ptr, *pend = p + RSTRING(str)->len, *s; char *ptr = p; long len = RSTRING(str)->len, rslen; VALUE line; if (rb_scan_args(argc, argv, "01", &rs) == 0) { rs = rb_rs; } if (NIL_P(rs)) { rb_yield(str); return str; } StringValue(rs); rslen = RSTRING(rs)->len; if (rslen == 0) { newline = '\n'; } else { newline = RSTRING(rs)->ptr[rslen-1]; } for (s = p, p += rslen; p < pend; p++) { if (rslen == 0 && *p == '\n') { if (*++p != '\n') continue; while (*p == '\n') p++; } if (RSTRING(str)->ptr < p && p[-1] == newline && (rslen <= 1 || rb_memcmp(RSTRING(rs)->ptr, p-rslen, rslen) == 0)) { line = rb_str_new5(str, s, p - s); OBJ_INFECT(line, str); rb_yield(line); str_mod_check(str, ptr, len); s = p; } } if (s != pend) { if (p > pend) p = pend; line = rb_str_new5(str, s, p - s); OBJ_INFECT(line, str); rb_yield(line); } return str; } /* * 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(str) VALUE str; { long i; for (i=0; ilen; i++) { rb_yield(INT2FIX(RSTRING(str)->ptr[i] & 0xff)); } return str; } /* * 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(str) VALUE str; { if (RSTRING(str)->len > 0) { rb_str_modify(str); RSTRING(str)->len--; if (RSTRING(str)->ptr[RSTRING(str)->len] == '\n') { if (RSTRING(str)->len > 0 && RSTRING(str)->ptr[RSTRING(str)->len-1] == '\r') { RSTRING(str)->len--; } } RSTRING(str)->ptr[RSTRING(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(str) VALUE str; { str = rb_str_dup(str); rb_str_chop_bang(str); return str; } /* * call-seq: * chop! => $_ or nil * * Equivalent to $_.chop!. * * a = "now\r\n" * $_ = a * chop! #=> "now" * chop! #=> "no" * chop! #=> "n" * chop! #=> "" * chop! #=> nil * $_ #=> "" * a #=> "" */ static VALUE rb_f_chop_bang(str) VALUE str; { return rb_str_chop_bang(uscore_get()); } /* * call-seq: * chop => string * * Equivalent to ($_.dup).chop!, except nil * is never returned. See String#chop!. * * a = "now\r\n" * $_ = a * chop #=> "now" * $_ #=> "now" * chop #=> "no" * chop #=> "n" * chop #=> "" * chop #=> "" * a #=> "now\r\n" */ static VALUE rb_f_chop() { VALUE str = uscore_get(); if (RSTRING(str)->len > 0) { str = rb_str_dup(str); rb_str_chop_bang(str); rb_lastline_set(str); } return str; } /* * 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(argc, argv, str) int argc; VALUE *argv; VALUE str; { VALUE rs; int newline; char *p; long len, rslen; if (rb_scan_args(argc, argv, "01", &rs) == 0) { len = RSTRING(str)->len; if (len == 0) return Qnil; p = RSTRING(str)->ptr; rs = rb_rs; if (rs == rb_default_rs) { smart_chomp: rb_str_modify(str); if (RSTRING(str)->ptr[len-1] == '\n') { RSTRING(str)->len--; if (RSTRING(str)->len > 0 && RSTRING(str)->ptr[RSTRING(str)->len-1] == '\r') { RSTRING(str)->len--; } } else if (RSTRING(str)->ptr[len-1] == '\r') { RSTRING(str)->len--; } else { return Qnil; } RSTRING(str)->ptr[RSTRING(str)->len] = '\0'; return str; } } if (NIL_P(rs)) return Qnil; StringValue(rs); len = RSTRING(str)->len; if (len == 0) return Qnil; p = RSTRING(str)->ptr; rslen = RSTRING(rs)->len; if (rslen == 0) { while (len>0 && p[len-1] == '\n') { len--; if (len>0 && p[len-1] == '\r') len--; } if (len < RSTRING(str)->len) { rb_str_modify(str); RSTRING(str)->len = len; RSTRING(str)->ptr[len] = '\0'; return str; } return Qnil; } if (rslen > len) return Qnil; newline = RSTRING(rs)->ptr[rslen-1]; if (rslen == 1 && newline == '\n') goto smart_chomp; if (p[len-1] == newline && (rslen <= 1 || rb_memcmp(RSTRING(rs)->ptr, p+len-rslen, rslen) == 0)) { rb_str_modify(str); RSTRING(str)->len -= rslen; RSTRING(str)->ptr[RSTRING(str)->len] = '\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(argc, argv, str) int argc; VALUE *argv; VALUE str; { str = rb_str_dup(str); rb_str_chomp_bang(argc, argv, str); return str; } /* * call-seq: * chomp! => $_ or nil * chomp!(string) => $_ or nil * * Equivalent to $_.chomp!(string). See * String#chomp! * * $_ = "now\n" * chomp! #=> "now" * $_ #=> "now" * chomp! "x" #=> nil * $_ #=> "now" */ static VALUE rb_f_chomp_bang(argc, argv) int argc; VALUE *argv; { return rb_str_chomp_bang(argc, argv, uscore_get()); } /* * call-seq: * chomp => $_ * chomp(string) => $_ * * Equivalent to $_ = $_.chomp(string). See * String#chomp. * * $_ = "now\n" * chomp #=> "now" * $_ #=> "now" * chomp "ow" #=> "n" * $_ #=> "n" * chomp "xxx" #=> "n" * $_ #=> "n" */ static VALUE rb_f_chomp(argc, argv) int argc; VALUE *argv; { VALUE str = uscore_get(); VALUE dup = rb_str_dup(str); if (NIL_P(rb_str_chomp_bang(argc, argv, dup))) return str; rb_lastline_set(dup); return dup; } /* * 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(str) VALUE str; { char *s, *t, *e; s = RSTRING(str)->ptr; if (!s || RSTRING(str)->len == 0) return Qnil; e = t = s + RSTRING(str)->len; /* remove spaces at head */ while (s < t && ISSPACE(*s)) s++; if (s > RSTRING(str)->ptr) { rb_str_modify(str); RSTRING(str)->len = t-s; memmove(RSTRING(str)->ptr, s, RSTRING(str)->len); RSTRING(str)->ptr[RSTRING(str)->len] = '\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(str) 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(str) VALUE str; { char *s, *t, *e; s = RSTRING(str)->ptr; if (!s || RSTRING(str)->len == 0) return Qnil; e = t = s + RSTRING(str)->len; /* remove trailing '\0's */ while (s < t && t[-1] == '\0') t--; /* remove trailing spaces */ while (s < t && ISSPACE(*(t-1))) t--; if (t < e) { rb_str_modify(str); RSTRING(str)->len = t-s; RSTRING(str)->ptr[RSTRING(str)->len] = '\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(str) 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(str) 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(str) VALUE str; { str = rb_str_dup(str); rb_str_strip_bang(str); return str; } static VALUE scan_once(str, pat, start) VALUE str, pat; long *start; { VALUE result, match; struct re_registers *regs; long i; if (rb_reg_search(pat, str, *start, 0) >= 0) { match = rb_backref_get(); regs = RMATCH(match)->regs; if (BEG(0) == END(0)) { /* * Always consume at least one character of the input string */ if (RSTRING(str)->len > END(0)) *start = END(0)+mbclen2(RSTRING(str)->ptr[END(0)],pat); 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(str, pat) VALUE str, pat; { VALUE result; long start = 0; VALUE match = Qnil; char *p = RSTRING(str)->ptr; long len = RSTRING(str)->len; 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: * scan(pattern) => array * scan(pattern) {|///| block } => $_ * * Equivalent to calling $_.scan. See * String#scan. */ static VALUE rb_f_scan(self, pat) VALUE self, pat; { return rb_str_scan(uscore_get(), pat); } /* * 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(str) 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(str) 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(str, salt) VALUE str, salt; { extern char *crypt(); VALUE result; char *s; StringValue(salt); if (RSTRING(salt)->len < 2) rb_raise(rb_eArgError, "salt too short(need >=2 bytes)"); if (RSTRING(str)->ptr) s = RSTRING(str)->ptr; else s = ""; result = rb_str_new2(crypt(s, RSTRING(salt)->ptr)); 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(s) VALUE s; { volatile VALUE str = s; ID id; if (!RSTRING(str)->ptr || RSTRING(str)->len == 0) { rb_raise(rb_eArgError, "interning empty string"); } if (strlen(RSTRING(str)->ptr) != RSTRING(str)->len) rb_raise(rb_eArgError, "symbol string may not contain `\\0'"); if (OBJ_TAINTED(str) && rb_safe_level() >= 1 && !rb_sym_interned_p(str)) { rb_raise(rb_eSecurityError, "Insecure: can't intern tainted string"); } id = rb_intern(RSTRING(str)->ptr); return ID2SYM(id); } /* * 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(argc, argv, str) 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(str)->ptr; len = RSTRING(str)->len; 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)<ptr; flen = RSTRING(pad)->len; if (flen == 0) { rb_raise(rb_eArgError, "zero width padding"); } } if (width < 0 || RSTRING(str)->len >= width) return rb_str_dup(str); res = rb_str_new5(str, 0, width); p = RSTRING(res)->ptr; if (jflag != 'l') { n = width - RSTRING(str)->len; pend = p + ((jflag == 'r') ? n : n/2); if (flen <= 1) { while (p < pend) { *p++ = *f; } } else { char *q = f; while (p + flen <= pend) { memcpy(p,f,flen); p += flen; } while (p < pend) { *p++ = *q++; } } } memcpy(p, RSTRING(str)->ptr, RSTRING(str)->len); if (jflag != 'r') { p += RSTRING(str)->len; pend = RSTRING(res)->ptr + width; if (flen <= 1) { while (p < pend) { *p++ = *f; } } else { while (p + flen <= pend) { memcpy(p,f,flen); p += flen; } while (p < pend) { *p++ = *f++; } } } OBJ_INFECT(res, str); if (flen > 0) OBJ_INFECT(res, pad); 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(argc, argv, str) 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(argc, argv, str) 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(argc, argv, str) int argc; VALUE *argv; VALUE str; { return rb_str_justify(argc, argv, str, 'c'); } void rb_str_setter(val, id, var) VALUE val; ID id; VALUE *var; { if (!NIL_P(val) && TYPE(val) != T_STRING) { rb_raise(rb_eTypeError, "value of %s must be String", rb_id2name(id)); } *var = val; } /* * 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() { rb_cString = rb_define_class("String", rb_cObject); rb_include_module(rb_cString, rb_mComparable); rb_include_module(rb_cString, rb_mEnumerable); rb_define_alloc_func(rb_cString, rb_str_s_alloc); 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, "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_m, 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, "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, "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, "include?", rb_str_include, 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", rb_str_each_line, -1); rb_define_method(rb_cString, "each_byte", rb_str_each_byte, 0); rb_define_method(rb_cString, "sum", rb_str_sum, -1); rb_define_global_function("sub", rb_f_sub, -1); rb_define_global_function("gsub", rb_f_gsub, -1); rb_define_global_function("sub!", rb_f_sub_bang, -1); rb_define_global_function("gsub!", rb_f_gsub_bang, -1); rb_define_global_function("chop", rb_f_chop, 0); rb_define_global_function("chop!", rb_f_chop_bang, 0); rb_define_global_function("chomp", rb_f_chomp, -1); rb_define_global_function("chomp!", rb_f_chomp_bang, -1); rb_define_global_function("split", rb_f_split, -1); rb_define_global_function("scan", rb_f_scan, 1); rb_define_method(rb_cString, "slice", rb_str_aref_m, -1); rb_define_method(rb_cString, "slice!", rb_str_slice_bang, -1); id_to_s = rb_intern("to_s"); rb_fs = Qnil; rb_define_variable("$;", &rb_fs); rb_define_variable("$-F", &rb_fs); } /********************************************************************** struct.c - $Author: shyouhei $ $Date: 2008-06-15 15:44:44 +0200 (Sun, 15 Jun 2008) $ created at: Tue Mar 22 18:44:30 JST 1995 Copyright (C) 1993-2003 Yukihiro Matsumoto **********************************************************************/ #include "ruby.h" #include "env.h" VALUE rb_cStruct; static VALUE struct_alloc _((VALUE)); VALUE rb_struct_iv_get(c, name) VALUE c; char *name; { ID id; id = rb_intern(name); for (;;) { if (rb_ivar_defined(c, id)) return rb_ivar_get(c, id); c = RCLASS(c)->super; if (c == 0 || c == rb_cStruct) return Qnil; } } VALUE rb_struct_s_members(klass) VALUE klass; { VALUE members = rb_struct_iv_get(klass, "__members__"); if (NIL_P(members)) { rb_bug("non-initialized struct"); } return members; } VALUE rb_struct_members(s) VALUE s; { VALUE members = rb_struct_s_members(rb_obj_class(s)); if (RSTRUCT(s)->len != RARRAY(members)->len) { rb_raise(rb_eTypeError, "struct size differs (%d required %d given)", RARRAY(members)->len, RSTRUCT(s)->len); } return members; } static VALUE rb_struct_s_members_m(klass) VALUE klass; { VALUE members, ary; VALUE *p, *pend; members = rb_struct_s_members(klass); ary = rb_ary_new2(RARRAY(members)->len); p = RARRAY(members)->ptr; pend = p + RARRAY(members)->len; while (p < pend) { rb_ary_push(ary, rb_str_new2(rb_id2name(SYM2ID(*p)))); p++; } return ary; } /* * call-seq: * struct.members => array * * Returns an array of strings representing the names of the instance * variables. * * Customer = Struct.new(:name, :address, :zip) * joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345) * joe.members #=> ["name", "address", "zip"] */ static VALUE rb_struct_members_m(obj) VALUE obj; { return rb_struct_s_members_m(rb_obj_class(obj)); } VALUE rb_struct_getmember(obj, id) VALUE obj; ID id; { VALUE members, slot; long i; members = rb_struct_members(obj); slot = ID2SYM(id); for (i=0; ilen; i++) { if (RARRAY(members)->ptr[i] == slot) { return RSTRUCT(obj)->ptr[i]; } } rb_name_error(id, "%s is not struct member", rb_id2name(id)); return Qnil; /* not reached */ } static VALUE rb_struct_ref(obj) VALUE obj; { return rb_struct_getmember(obj, ruby_frame->orig_func); } static VALUE rb_struct_ref0(obj) VALUE obj; {return RSTRUCT(obj)->ptr[0];} static VALUE rb_struct_ref1(obj) VALUE obj; {return RSTRUCT(obj)->ptr[1];} static VALUE rb_struct_ref2(obj) VALUE obj; {return RSTRUCT(obj)->ptr[2];} static VALUE rb_struct_ref3(obj) VALUE obj; {return RSTRUCT(obj)->ptr[3];} static VALUE rb_struct_ref4(obj) VALUE obj; {return RSTRUCT(obj)->ptr[4];} static VALUE rb_struct_ref5(obj) VALUE obj; {return RSTRUCT(obj)->ptr[5];} static VALUE rb_struct_ref6(obj) VALUE obj; {return RSTRUCT(obj)->ptr[6];} static VALUE rb_struct_ref7(obj) VALUE obj; {return RSTRUCT(obj)->ptr[7];} static VALUE rb_struct_ref8(obj) VALUE obj; {return RSTRUCT(obj)->ptr[8];} static VALUE rb_struct_ref9(obj) VALUE obj; {return RSTRUCT(obj)->ptr[9];} static VALUE (*ref_func[10])() = { rb_struct_ref0, rb_struct_ref1, rb_struct_ref2, rb_struct_ref3, rb_struct_ref4, rb_struct_ref5, rb_struct_ref6, rb_struct_ref7, rb_struct_ref8, rb_struct_ref9, }; static void rb_struct_modify(s) VALUE s; { if (OBJ_FROZEN(s)) rb_error_frozen("Struct"); if (!OBJ_TAINTED(s) && rb_safe_level() >= 4) rb_raise(rb_eSecurityError, "Insecure: can't modify Struct"); } static VALUE rb_struct_set(obj, val) VALUE obj, val; { VALUE members, slot; ID id; long i; members = rb_struct_members(obj); rb_struct_modify(obj); id = ruby_frame->orig_func; for (i=0; ilen; i++) { slot = RARRAY(members)->ptr[i]; if (rb_id_attrset(SYM2ID(slot)) == id) { return RSTRUCT(obj)->ptr[i] = val; } } rb_name_error(ruby_frame->last_func, "`%s' is not a struct member", rb_id2name(id)); return Qnil; /* not reached */ } static VALUE make_struct(name, members, klass) VALUE name, members, klass; { VALUE nstr; ID id; long i; OBJ_FREEZE(members); if (NIL_P(name)) { nstr = rb_class_new(klass); rb_make_metaclass(nstr, RBASIC(klass)->klass); rb_class_inherited(klass, nstr); } else { char *cname = StringValuePtr(name); id = rb_intern(cname); if (!rb_is_const_id(id)) { rb_name_error(id, "identifier %s needs to be constant", cname); } if (rb_const_defined_at(klass, id)) { rb_warn("redefining constant Struct::%s", cname); rb_mod_remove_const(klass, ID2SYM(id)); } nstr = rb_define_class_under(klass, rb_id2name(id), klass); } rb_iv_set(nstr, "__size__", LONG2NUM(RARRAY(members)->len)); rb_iv_set(nstr, "__members__", members); rb_define_alloc_func(nstr, struct_alloc); rb_define_singleton_method(nstr, "new", rb_class_new_instance, -1); rb_define_singleton_method(nstr, "[]", rb_class_new_instance, -1); rb_define_singleton_method(nstr, "members", rb_struct_s_members_m, 0); for (i=0; i< RARRAY(members)->len; i++) { ID id = SYM2ID(RARRAY(members)->ptr[i]); if (rb_is_local_id(id) || rb_is_const_id(id)) { if (i<10) { rb_define_method_id(nstr, id, ref_func[i], 0); } else { rb_define_method_id(nstr, id, rb_struct_ref, 0); } rb_define_method_id(nstr, rb_id_attrset(id), rb_struct_set, 1); } } return nstr; } #ifdef HAVE_STDARG_PROTOTYPES #include #define va_init_list(a,b) va_start(a,b) #else #include #define va_init_list(a,b) va_start(a) #endif VALUE #ifdef HAVE_STDARG_PROTOTYPES rb_struct_define(const char *name, ...) #else rb_struct_define(name, va_alist) const char *name; va_dcl #endif { va_list ar; VALUE nm, ary; char *mem; if (!name) nm = Qnil; else nm = rb_str_new2(name); ary = rb_ary_new(); va_init_list(ar, name); while ((mem = va_arg(ar, char*)) != 0) { ID slot = rb_intern(mem); rb_ary_push(ary, ID2SYM(slot)); } va_end(ar); return make_struct(nm, ary, rb_cStruct); } /* * call-seq: * Struct.new( [aString] [, aSym]+> ) => StructClass * StructClass.new(arg, ...) => obj * StructClass[arg, ...] => obj * * Creates a new class, named by aString, containing accessor * methods for the given symbols. If the name aString is * omitted, an anonymous structure class will be created. Otherwise, * the name of this struct will appear as a constant in class * Struct, so it must be unique for all * Structs in the system and should start with a capital * letter. Assigning a structure class to a constant effectively gives * the class the name of the constant. * * Struct::new returns a new Class object, * which can then be used to create specific instances of the new * structure. The number of actual parameters must be * less than or equal to the number of attributes defined for this * class; unset parameters default to \nil{}. Passing too many * parameters will raise an \E{ArgumentError}. * * The remaining methods listed in this section (class and instance) * are defined for this generated class. * * # Create a structure with a name in Struct * Struct.new("Customer", :name, :address) #=> Struct::Customer * Struct::Customer.new("Dave", "123 Main") #=> # * * # Create a structure named by its constant * Customer = Struct.new(:name, :address) #=> Customer * Customer.new("Dave", "123 Main") #=> # */ static VALUE rb_struct_s_def(argc, argv, klass) int argc; VALUE *argv; VALUE klass; { VALUE name, rest; long i; VALUE st; ID id; rb_scan_args(argc, argv, "1*", &name, &rest); if (!NIL_P(name) && SYMBOL_P(name)) { rb_ary_unshift(rest, name); name = Qnil; } for (i=0; ilen; i++) { id = rb_to_id(RARRAY(rest)->ptr[i]); RARRAY(rest)->ptr[i] = ID2SYM(id); } st = make_struct(name, rest, klass); if (rb_block_given_p()) { rb_mod_module_eval(0, 0, st); } return st; } /* */ static VALUE rb_struct_initialize(self, values) VALUE self, values; { VALUE klass = rb_obj_class(self); VALUE size; long n; rb_struct_modify(self); size = rb_struct_iv_get(klass, "__size__"); n = FIX2LONG(size); if (n < RARRAY(values)->len) { rb_raise(rb_eArgError, "struct size differs"); } MEMCPY(RSTRUCT(self)->ptr, RARRAY(values)->ptr, VALUE, RARRAY(values)->len); if (n > RARRAY(values)->len) { rb_mem_clear(RSTRUCT(self)->ptr+RARRAY(values)->len, n-RARRAY(values)->len); } return Qnil; } static VALUE struct_alloc(klass) VALUE klass; { VALUE size; long n; NEWOBJ(st, struct RStruct); OBJSETUP(st, klass, T_STRUCT); size = rb_struct_iv_get(klass, "__size__"); n = FIX2LONG(size); st->ptr = ALLOC_N(VALUE, n); rb_mem_clear(st->ptr, n); st->len = n; return (VALUE)st; } VALUE rb_struct_alloc(klass, values) VALUE klass, values; { return rb_class_new_instance(RARRAY(values)->len, RARRAY(values)->ptr, klass); } VALUE #ifdef HAVE_STDARG_PROTOTYPES rb_struct_new(VALUE klass, ...) #else rb_struct_new(klass, va_alist) VALUE klass; va_dcl #endif { VALUE sz, *mem; long size, i; va_list args; sz = rb_struct_iv_get(klass, "__size__"); size = FIX2LONG(sz); mem = ALLOCA_N(VALUE, size); va_init_list(args, klass); for (i=0; i struct * * Calls block once for each instance variable, passing the * value as a parameter. * * Customer = Struct.new(:name, :address, :zip) * joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345) * joe.each {|x| puts(x) } * * produces: * * Joe Smith * 123 Maple, Anytown NC * 12345 */ static VALUE rb_struct_each(s) VALUE s; { long i; for (i=0; ilen; i++) { rb_yield(RSTRUCT(s)->ptr[i]); } return s; } /* * call-seq: * struct.each_pair {|sym, obj| block } => struct * * Calls block once for each instance variable, passing the name * (as a symbol) and the value as parameters. * * Customer = Struct.new(:name, :address, :zip) * joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345) * joe.each_pair {|name, value| puts("#{name} => #{value}") } * * produces: * * name => Joe Smith * address => 123 Maple, Anytown NC * zip => 12345 */ static VALUE rb_struct_each_pair(s) VALUE s; { VALUE members; long i; members = rb_struct_members(s); for (i=0; ilen; i++) { rb_yield_values(2, rb_ary_entry(members, i), RSTRUCT(s)->ptr[i]); } return s; } static VALUE inspect_struct(s) VALUE s; { char *cname = rb_class2name(rb_obj_class(s)); VALUE str, members; long i; members = rb_struct_members(s); str = rb_str_buf_new2("#len; i++) { VALUE slot; ID id; char *p; if (i > 0) { rb_str_cat2(str, ", "); } slot = RARRAY(members)->ptr[i]; id = SYM2ID(slot); if (rb_is_local_id(id) || rb_is_const_id(id)) { p = rb_id2name(id); rb_str_cat2(str, p); } else { rb_str_append(str, rb_inspect(slot)); } rb_str_cat2(str, "="); rb_str_append(str, rb_inspect(RSTRUCT(s)->ptr[i])); } rb_str_cat2(str, ">"); OBJ_INFECT(str, s); return str; } /* * call-seq: * struct.to_s => string * struct.inspect => string * * Describe the contents of this struct in a string. */ static VALUE rb_struct_inspect(s) VALUE s; { if (rb_inspecting_p(s)) { char *cname = rb_class2name(rb_obj_class(s)); size_t len = strlen(cname) + 14; VALUE str = rb_str_new(0, len); snprintf(RSTRING(str)->ptr, len+1, "#", cname); RSTRING(str)->len = strlen(RSTRING(str)->ptr); return str; } return rb_protect_inspect(inspect_struct, s, 0); } /* * call-seq: * struct.to_a => array * struct.values => array * * Returns the values for this instance as an array. * * Customer = Struct.new(:name, :address, :zip) * joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345) * joe.to_a[1] #=> "123 Maple, Anytown NC" */ static VALUE rb_struct_to_a(s) VALUE s; { return rb_ary_new4(RSTRUCT(s)->len, RSTRUCT(s)->ptr); } /* :nodoc: */ static VALUE rb_struct_init_copy(copy, s) VALUE copy, s; { if (copy == s) return copy; rb_check_frozen(copy); if (!rb_obj_is_instance_of(s, rb_obj_class(copy))) { rb_raise(rb_eTypeError, "wrong argument class"); } if (RSTRUCT(copy)->len != RSTRUCT(s)->len) { rb_raise(rb_eTypeError, "struct size mismatch"); } MEMCPY(RSTRUCT(copy)->ptr, RSTRUCT(s)->ptr, VALUE, RSTRUCT(copy)->len); return copy; } static VALUE rb_struct_aref_id(s, id) VALUE s; ID id; { VALUE members; long i, len; members = rb_struct_members(s); len = RARRAY(members)->len; for (i=0; iptr[i]) == id) { return RSTRUCT(s)->ptr[i]; } } rb_name_error(id, "no member '%s' in struct", rb_id2name(id)); return Qnil; /* not reached */ } /* * call-seq: * struct[symbol] => anObject * struct[fixnum] => anObject * * Attribute Reference---Returns the value of the instance variable * named by symbol, or indexed (0..length-1) by * fixnum. Will raise NameError if the named * variable does not exist, or IndexError if the index is * out of range. * * Customer = Struct.new(:name, :address, :zip) * joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345) * * joe["name"] #=> "Joe Smith" * joe[:name] #=> "Joe Smith" * joe[0] #=> "Joe Smith" */ VALUE rb_struct_aref(s, idx) VALUE s, idx; { long i; if (TYPE(idx) == T_STRING || TYPE(idx) == T_SYMBOL) { return rb_struct_aref_id(s, rb_to_id(idx)); } i = NUM2LONG(idx); if (i < 0) i = RSTRUCT(s)->len + i; if (i < 0) rb_raise(rb_eIndexError, "offset %ld too small for struct(size:%ld)", i, RSTRUCT(s)->len); if (RSTRUCT(s)->len <= i) rb_raise(rb_eIndexError, "offset %ld too large for struct(size:%ld)", i, RSTRUCT(s)->len); return RSTRUCT(s)->ptr[i]; } static VALUE rb_struct_aset_id(s, id, val) VALUE s, val; ID id; { VALUE members; long i, len; members = rb_struct_members(s); rb_struct_modify(s); len = RARRAY(members)->len; if (RSTRUCT(s)->len != RARRAY(members)->len) { rb_raise(rb_eTypeError, "struct size differs (%d required %d given)", RARRAY(members)->len, RSTRUCT(s)->len); } for (i=0; iptr[i]) == id) { RSTRUCT(s)->ptr[i] = val; return val; } } rb_name_error(id, "no member '%s' in struct", rb_id2name(id)); } /* * call-seq: * struct[symbol] = obj => obj * struct[fixnum] = obj => obj * * Attribute Assignment---Assigns to the instance variable named by * symbol or fixnum the value obj and * returns it. Will raise a NameError if the named * variable does not exist, or an IndexError if the index * is out of range. * * Customer = Struct.new(:name, :address, :zip) * joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345) * * joe["name"] = "Luke" * joe[:zip] = "90210" * * joe.name #=> "Luke" * joe.zip #=> "90210" */ VALUE rb_struct_aset(s, idx, val) VALUE s, idx, val; { long i; if (TYPE(idx) == T_STRING || TYPE(idx) == T_SYMBOL) { return rb_struct_aset_id(s, rb_to_id(idx), val); } i = NUM2LONG(idx); if (i < 0) i = RSTRUCT(s)->len + i; if (i < 0) { rb_raise(rb_eIndexError, "offset %ld too small for struct(size:%ld)", i, RSTRUCT(s)->len); } if (RSTRUCT(s)->len <= i) { rb_raise(rb_eIndexError, "offset %ld too large for struct(size:%ld)", i, RSTRUCT(s)->len); } rb_struct_modify(s); return RSTRUCT(s)->ptr[i] = val; } static VALUE struct_entry _((VALUE, long)); static VALUE struct_entry(s, n) VALUE s; long n; { return rb_struct_aref(s, LONG2NUM(n)); } /* * call-seq: * struct.values_at(selector,... ) => an_array * * Returns an array containing the elements in * _self_ corresponding to the given selector(s). The selectors * may be either integer indices or ranges. * See also .select. * * a = %w{ a b c d e f } * a.values_at(1, 3, 5) * a.values_at(1, 3, 5, 7) * a.values_at(-1, -3, -5, -7) * a.values_at(1..3, 2...5) */ static VALUE rb_struct_values_at(argc, argv, s) int argc; VALUE *argv; VALUE s; { return rb_values_at(s, RSTRUCT(s)->len, argc, argv, struct_entry); } /* * call-seq: * struct.select {|i| block } => array * * Invokes the block passing in successive elements from * struct, returning an array containing those elements * for which the block returns a true value (equivalent to * Enumerable#select). * * Lots = Struct.new(:a, :b, :c, :d, :e, :f) * l = Lots.new(11, 22, 33, 44, 55, 66) * l.select {|v| (v % 2).zero? } #=> [22, 44, 66] */ static VALUE rb_struct_select(argc, argv, s) int argc; VALUE *argv; VALUE s; { VALUE result; long i; if (argc > 0) { rb_raise(rb_eArgError, "wrong number of arguments (%d for 0)", argc); } result = rb_ary_new(); for (i = 0; i < RSTRUCT(s)->len; i++) { if (RTEST(rb_yield(RSTRUCT(s)->ptr[i]))) { rb_ary_push(result, RSTRUCT(s)->ptr[i]); } } return result; } /* * call-seq: * struct == other_struct => true or false * * Equality---Returns true if other_struct is * equal to this one: they must be of the same class as generated by * Struct::new, and the values of all instance variables * must be equal (according to Object#==). * * Customer = Struct.new(:name, :address, :zip) * joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345) * joejr = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345) * jane = Customer.new("Jane Doe", "456 Elm, Anytown NC", 12345) * joe == joejr #=> true * joe == jane #=> false */ static VALUE rb_struct_equal(s, s2) VALUE s, s2; { long i; if (s == s2) return Qtrue; if (TYPE(s2) != T_STRUCT) return Qfalse; if (rb_obj_class(s) != rb_obj_class(s2)) return Qfalse; if (RSTRUCT(s)->len != RSTRUCT(s2)->len) { rb_bug("inconsistent struct"); /* should never happen */ } for (i=0; ilen; i++) { if (!rb_equal(RSTRUCT(s)->ptr[i], RSTRUCT(s2)->ptr[i])) return Qfalse; } return Qtrue; } /* * call-seq: * struct.hash => fixnum * * Return a hash value based on this struct's contents. */ static VALUE rb_struct_hash(s) VALUE s; { long i, h; VALUE n; h = rb_hash(rb_obj_class(s)); for (i = 0; i < RSTRUCT(s)->len; i++) { h = (h << 1) | (h<0 ? 1 : 0); n = rb_hash(RSTRUCT(s)->ptr[i]); h ^= NUM2LONG(n); } return LONG2FIX(h); } /* * code-seq: * struct.eql?(other) => true or false * * Two structures are equal if they are the same object, or if all their * fields are equal (using eql?). */ static VALUE rb_struct_eql(s, s2) VALUE s, s2; { long i; if (s == s2) return Qtrue; if (TYPE(s2) != T_STRUCT) return Qfalse; if (rb_obj_class(s) != rb_obj_class(s2)) return Qfalse; if (RSTRUCT(s)->len != RSTRUCT(s2)->len) { rb_bug("inconsistent struct"); /* should never happen */ } for (i=0; ilen; i++) { if (!rb_eql(RSTRUCT(s)->ptr[i], RSTRUCT(s2)->ptr[i])) return Qfalse; } return Qtrue; } /* * call-seq: * struct.length => fixnum * struct.size => fixnum * * Returns the number of instance variables. * * Customer = Struct.new(:name, :address, :zip) * joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345) * joe.length #=> 3 */ static VALUE rb_struct_size(s) VALUE s; { return LONG2FIX(RSTRUCT(s)->len); } /* * A Struct is a convenient way to bundle a number of * attributes together, using accessor methods, without having to write * an explicit class. * * The Struct class is a generator of specific classes, * each one of which is defined to hold a set of variables and their * accessors. In these examples, we'll call the generated class * ``CustomerClass,'' and we'll show an example instance of that * class as ``CustomerInst.'' * * In the descriptions that follow, the parameter symbol refers * to a symbol, which is either a quoted string or a * Symbol (such as :name). */ void Init_Struct() { rb_cStruct = rb_define_class("Struct", rb_cObject); rb_include_module(rb_cStruct, rb_mEnumerable); rb_undef_alloc_func(rb_cStruct); rb_define_singleton_method(rb_cStruct, "new", rb_struct_s_def, -1); rb_define_method(rb_cStruct, "initialize", rb_struct_initialize, -2); rb_define_method(rb_cStruct, "initialize_copy", rb_struct_init_copy, 1); rb_define_method(rb_cStruct, "==", rb_struct_equal, 1); rb_define_method(rb_cStruct, "eql?", rb_struct_eql, 1); rb_define_method(rb_cStruct, "hash", rb_struct_hash, 0); rb_define_method(rb_cStruct, "to_s", rb_struct_inspect, 0); rb_define_method(rb_cStruct, "inspect", rb_struct_inspect, 0); rb_define_method(rb_cStruct, "to_a", rb_struct_to_a, 0); rb_define_method(rb_cStruct, "values", rb_struct_to_a, 0); rb_define_method(rb_cStruct, "size", rb_struct_size, 0); rb_define_method(rb_cStruct, "length", rb_struct_size, 0); rb_define_method(rb_cStruct, "each", rb_struct_each, 0); rb_define_method(rb_cStruct, "each_pair", rb_struct_each_pair, 0); rb_define_method(rb_cStruct, "[]", rb_struct_aref, 1); rb_define_method(rb_cStruct, "[]=", rb_struct_aset, 2); rb_define_method(rb_cStruct, "select", rb_struct_select, -1); rb_define_method(rb_cStruct, "values_at", rb_struct_values_at, -1); rb_define_method(rb_cStruct, "members", rb_struct_members_m, 0); } /********************************************************************** time.c - $Author: shyouhei $ $Date: 2009-02-02 12:03:25 +0100 (Mon, 02 Feb 2009) $ created at: Tue Dec 28 14:31:59 JST 1993 Copyright (C) 1993-2003 Yukihiro Matsumoto **********************************************************************/ #include "ruby.h" #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include VALUE rb_cTime; struct time_object { struct timeval tv; struct tm tm; int gmt; int tm_got; }; #define GetTimeval(obj, tobj) \ Data_Get_Struct(obj, struct time_object, tobj) static void time_free _((void *)); static VALUE time_utc_offset _((VALUE)); static void time_free(tobj) void *tobj; { if (tobj) free(tobj); } static VALUE time_s_alloc _((VALUE)); static VALUE time_s_alloc(klass) VALUE klass; { VALUE obj; struct time_object *tobj; obj = Data_Make_Struct(klass, struct time_object, 0, time_free, tobj); tobj->tm_got=0; tobj->tv.tv_sec = 0; tobj->tv.tv_usec = 0; return obj; } static void time_modify(time) VALUE time; { rb_check_frozen(time); if (!OBJ_TAINTED(time) && rb_safe_level() >= 4) rb_raise(rb_eSecurityError, "Insecure: can't modify Time"); } /* * Document-method: now * * Synonym for Time.new. Returns a +Time+ object * initialized tot he current system time. * * call-seq: * Time.new -> time * * Returns a Time object initialized to the current system * time. Note: The object created will be created using the * resolution available on your system clock, and so may include * fractional seconds. * * a = Time.new #=> Wed Apr 09 08:56:03 CDT 2003 * b = Time.new #=> Wed Apr 09 08:56:03 CDT 2003 * a == b #=> false * "%.6f" % a.to_f #=> "1049896563.230740" * "%.6f" % b.to_f #=> "1049896563.231466" * */ static VALUE time_init(time) VALUE time; { struct time_object *tobj; time_modify(time); GetTimeval(time, tobj); tobj->tm_got=0; tobj->tv.tv_sec = 0; tobj->tv.tv_usec = 0; if (gettimeofday(&tobj->tv, 0) < 0) { rb_sys_fail("gettimeofday"); } return time; } #define NDIV(x,y) (-(-((x)+1)/(y))-1) #define NMOD(x,y) ((y)-(-((x)+1)%(y))-1) static void time_overflow_p(secp, usecp) time_t *secp, *usecp; { time_t tmp, sec = *secp, usec = *usecp; if (usec >= 1000000) { /* usec positive overflow */ tmp = sec + usec / 1000000; usec %= 1000000; if (sec > 0 && tmp < 0) { rb_raise(rb_eRangeError, "out of Time range"); } sec = tmp; } if (usec < 0) { /* usec negative overflow */ tmp = sec + NDIV(usec,1000000); /* negative div */ usec = NMOD(usec,1000000); /* negative mod */ if (sec < 0 && tmp > 0) { rb_raise(rb_eRangeError, "out of Time range"); } sec = tmp; } #ifndef NEGATIVE_TIME_T if (sec < 0 || (sec == 0 && usec < 0)) rb_raise(rb_eArgError, "time must be positive"); #endif *secp = sec; *usecp = usec; } static VALUE time_new_internal _((VALUE, time_t, time_t)); static VALUE time_new_internal(klass, sec, usec) VALUE klass; time_t sec, usec; { VALUE time = time_s_alloc(klass); struct time_object *tobj; GetTimeval(time, tobj); time_overflow_p(&sec, &usec); tobj->tv.tv_sec = sec; tobj->tv.tv_usec = usec; return time; } VALUE rb_time_new(sec, usec) time_t sec, usec; { return time_new_internal(rb_cTime, sec, usec); } static struct timeval time_timeval(time, interval) VALUE time; int interval; { struct timeval t; char *tstr = interval ? "time interval" : "time"; #ifndef NEGATIVE_TIME_T interval = 1; #endif switch (TYPE(time)) { case T_FIXNUM: t.tv_sec = FIX2LONG(time); if (interval && t.tv_sec < 0) rb_raise(rb_eArgError, "%s must be positive", tstr); t.tv_usec = 0; break; case T_FLOAT: if (interval && RFLOAT(time)->value < 0.0) rb_raise(rb_eArgError, "%s must be positive", tstr); else { double f, d; d = modf(RFLOAT(time)->value, &f); if (d < 0) { d += 1; f -= 1; } t.tv_sec = (time_t)f; if (f != t.tv_sec) { rb_raise(rb_eRangeError, "%f out of Time range", RFLOAT(time)->value); } t.tv_usec = (time_t)(d*1e6+0.5); } break; case T_BIGNUM: t.tv_sec = NUM2LONG(time); if (interval && t.tv_sec < 0) rb_raise(rb_eArgError, "%s must be positive", tstr); t.tv_usec = 0; break; default: rb_raise(rb_eTypeError, "can't convert %s into %s", rb_obj_classname(time), tstr); break; } return t; } struct timeval rb_time_interval(time) VALUE time; { return time_timeval(time, Qtrue); } struct timeval rb_time_timeval(time) VALUE time; { struct time_object *tobj; struct timeval t; if (TYPE(time) == T_DATA && RDATA(time)->dfree == time_free) { GetTimeval(time, tobj); t = tobj->tv; return t; } return time_timeval(time, Qfalse); } /* * call-seq: * Time.at( aTime ) => time * Time.at( seconds [, microseconds] ) => time * * Creates a new time object with the value given by aTime, or * the given number of seconds (and optional * microseconds) from epoch. A non-portable feature allows the * offset to be negative on some systems. * * Time.at(0) #=> Wed Dec 31 18:00:00 CST 1969 * Time.at(946702800) #=> Fri Dec 31 23:00:00 CST 1999 * Time.at(-284061600) #=> Sat Dec 31 00:00:00 CST 1960 */ static VALUE time_s_at(argc, argv, klass) int argc; VALUE *argv; VALUE klass; { struct timeval tv; VALUE time, t; if (rb_scan_args(argc, argv, "11", &time, &t) == 2) { tv.tv_sec = NUM2LONG(time); tv.tv_usec = NUM2LONG(t); } else { tv = rb_time_timeval(time); } t = time_new_internal(klass, tv.tv_sec, tv.tv_usec); if (TYPE(time) == T_DATA && RDATA(time)->dfree == time_free) { struct time_object *tobj, *tobj2; GetTimeval(time, tobj); GetTimeval(t, tobj2); tobj2->gmt = tobj->gmt; } return t; } static char *months [12] = { "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec", }; static long obj2long(obj) VALUE obj; { if (TYPE(obj) == T_STRING) { obj = rb_str_to_inum(obj, 10, Qfalse); } return NUM2LONG(obj); } static void time_arg(argc, argv, tm, usec) int argc; VALUE *argv; struct tm *tm; time_t *usec; { VALUE v[8]; int i; long year; MEMZERO(tm, struct tm, 1); *usec = 0; if (argc == 10) { v[0] = argv[5]; v[1] = argv[4]; v[2] = argv[3]; v[3] = argv[2]; v[4] = argv[1]; v[5] = argv[0]; v[6] = Qnil; tm->tm_isdst = RTEST(argv[8]) ? 1 : 0; } else { rb_scan_args(argc, argv, "17", &v[0],&v[1],&v[2],&v[3],&v[4],&v[5],&v[6],&v[7]); /* v[6] may be usec or zone (parsedate) */ /* v[7] is wday (parsedate; ignored) */ tm->tm_wday = -1; tm->tm_isdst = -1; } year = obj2long(v[0]); if (0 <= year && year < 39) { year += 100; rb_warning("2 digits year is used"); } else if (69 <= year && year < 139) { rb_warning("2 or 3 digits year is used"); } else { year -= 1900; } tm->tm_year = year; if (NIL_P(v[1])) { tm->tm_mon = 0; } else { VALUE s = rb_check_string_type(v[1]); if (!NIL_P(s)) { tm->tm_mon = -1; for (i=0; i<12; i++) { if (RSTRING(s)->len == 3 && strcasecmp(months[i], RSTRING(s)->ptr) == 0) { tm->tm_mon = i; break; } } if (tm->tm_mon == -1) { char c = RSTRING(s)->ptr[0]; if ('0' <= c && c <= '9') { tm->tm_mon = obj2long(s)-1; } } } else { tm->tm_mon = obj2long(v[1])-1; } } if (NIL_P(v[2])) { tm->tm_mday = 1; } else { tm->tm_mday = obj2long(v[2]); } tm->tm_hour = NIL_P(v[3])?0:obj2long(v[3]); tm->tm_min = NIL_P(v[4])?0:obj2long(v[4]); tm->tm_sec = NIL_P(v[5])?0:obj2long(v[5]); if (!NIL_P(v[6])) { if (argc == 8) { /* v[6] is timezone, but ignored */ } else if (argc == 7) { *usec = obj2long(v[6]); } } /* value validation */ if ( tm->tm_year != year || #ifndef NEGATIVE_TIME_T tm->tm_year < 69 || #endif tm->tm_mon < 0 || tm->tm_mon > 11 || tm->tm_mday < 1 || tm->tm_mday > 31 || tm->tm_hour < 0 || tm->tm_hour > 23 || tm->tm_min < 0 || tm->tm_min > 59 || tm->tm_sec < 0 || tm->tm_sec > 60) rb_raise(rb_eArgError, "argument out of range"); } static VALUE time_gmtime _((VALUE)); static VALUE time_localtime _((VALUE)); static VALUE time_get_tm _((VALUE, int)); static int leap_year_p(y) long y; { return ((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0); } #define DIV(n,d) ((n)<0 ? NDIV((n),(d)) : (n)/(d)) static time_t timegm_noleapsecond(tm) struct tm *tm; { static int common_year_yday_offset[] = { -1, -1 + 31, -1 + 31 + 28, -1 + 31 + 28 + 31, -1 + 31 + 28 + 31 + 30, -1 + 31 + 28 + 31 + 30 + 31, -1 + 31 + 28 + 31 + 30 + 31 + 30, -1 + 31 + 28 + 31 + 30 + 31 + 30 + 31, -1 + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31, -1 + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30, -1 + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, -1 + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 /* 1 2 3 4 5 6 7 8 9 10 11 */ }; static int leap_year_yday_offset[] = { -1, -1 + 31, -1 + 31 + 29, -1 + 31 + 29 + 31, -1 + 31 + 29 + 31 + 30, -1 + 31 + 29 + 31 + 30 + 31, -1 + 31 + 29 + 31 + 30 + 31 + 30, -1 + 31 + 29 + 31 + 30 + 31 + 30 + 31, -1 + 31 + 29 + 31 + 30 + 31 + 30 + 31 + 31, -1 + 31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30, -1 + 31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, -1 + 31 + 29 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 /* 1 2 3 4 5 6 7 8 9 10 11 */ }; long tm_year = tm->tm_year; int tm_yday = tm->tm_mday; if (leap_year_p(tm_year + 1900)) tm_yday += leap_year_yday_offset[tm->tm_mon]; else tm_yday += common_year_yday_offset[tm->tm_mon]; /* * `Seconds Since the Epoch' in SUSv3: * tm_sec + tm_min*60 + tm_hour*3600 + tm_yday*86400 + * (tm_year-70)*31536000 + ((tm_year-69)/4)*86400 - * ((tm_year-1)/100)*86400 + ((tm_year+299)/400)*86400 */ return tm->tm_sec + tm->tm_min*60 + tm->tm_hour*3600 + (time_t)(tm_yday + (tm_year-70)*365 + DIV(tm_year-69,4) - DIV(tm_year-1,100) + DIV(tm_year+299,400))*86400; } static int tmcmp(a, b) struct tm *a; struct tm *b; { if (a->tm_year != b->tm_year) return a->tm_year < b->tm_year ? -1 : 1; else if (a->tm_mon != b->tm_mon) return a->tm_mon < b->tm_mon ? -1 : 1; else if (a->tm_mday != b->tm_mday) return a->tm_mday < b->tm_mday ? -1 : 1; else if (a->tm_hour != b->tm_hour) return a->tm_hour < b->tm_hour ? -1 : 1; else if (a->tm_min != b->tm_min) return a->tm_min < b->tm_min ? -1 : 1; else if (a->tm_sec != b->tm_sec) return a->tm_sec < b->tm_sec ? -1 : 1; else return 0; } #if SIZEOF_TIME_T == SIZEOF_LONG typedef unsigned long unsigned_time_t; #elif SIZEOF_TIME_T == SIZEOF_INT typedef unsigned int unsigned_time_t; #elif SIZEOF_TIME_T == SIZEOF_LONG_LONG typedef unsigned LONG_LONG unsigned_time_t; #else # error cannot find integer type which size is same as time_t. #endif static time_t search_time_t(tptr, utc_p) struct tm *tptr; int utc_p; { time_t guess, guess_lo, guess_hi; struct tm *tm, tm_lo, tm_hi; int d, have_guess; int find_dst; find_dst = 0 < tptr->tm_isdst; #ifdef NEGATIVE_TIME_T guess_lo = (time_t)~((unsigned_time_t)~(time_t)0 >> 1); #else guess_lo = 0; #endif guess_hi = ((time_t)-1) < ((time_t)0) ? (time_t)((unsigned_time_t)~(time_t)0 >> 1) : ~(time_t)0; guess = timegm_noleapsecond(tptr); tm = (utc_p ? gmtime : localtime)(&guess); if (tm) { d = tmcmp(tptr, tm); if (d == 0) return guess; if (d < 0) { guess_hi = guess; guess -= 24 * 60 * 60; } else { guess_lo = guess; guess += 24 * 60 * 60; } if (guess_lo < guess && guess < guess_hi && (tm = (utc_p ? gmtime : localtime)(&guess)) != NULL) { d = tmcmp(tptr, tm); if (d == 0) return guess; if (d < 0) guess_hi = guess; else guess_lo = guess; } } tm = (utc_p ? gmtime : localtime)(&guess_lo); if (!tm) goto error; d = tmcmp(tptr, tm); if (d < 0) goto out_of_range; if (d == 0) return guess_lo; tm_lo = *tm; tm = (utc_p ? gmtime : localtime)(&guess_hi); if (!tm) goto error; d = tmcmp(tptr, tm); if (d > 0) goto out_of_range; if (d == 0) return guess_hi; tm_hi = *tm; have_guess = 0; while (guess_lo + 1 < guess_hi) { /* there is a gap between guess_lo and guess_hi. */ unsigned long range = 0; if (!have_guess) { int a, b; /* Try precious guess by a linear interpolation at first. `a' and `b' is a coefficient of guess_lo and guess_hi as: guess = (guess_lo * a + guess_hi * b) / (a + b) However this causes overflow in most cases, following assignment is used instead: guess = guess_lo / d * a + (guess_lo % d) * a / d + guess_hi / d * b + (guess_hi % d) * b / d where d = a + b To avoid overflow in this assignment, `d' is restricted to less than sqrt(2**31). By this restriction and other reasons, the guess is not accurate and some error is expected. `range' approximates the maximum error. When these parameters are not suitable, i.e. guess is not within guess_lo and guess_hi, simple guess by binary search is used. */ range = 366 * 24 * 60 * 60; a = (tm_hi.tm_year - tptr->tm_year); b = (tptr->tm_year - tm_lo.tm_year); /* 46000 is selected as `some big number less than sqrt(2**31)'. */ if (a + b <= 46000 / 12) { range = 31 * 24 * 60 * 60; a *= 12; b *= 12; a += tm_hi.tm_mon - tptr->tm_mon; b += tptr->tm_mon - tm_lo.tm_mon; if (a + b <= 46000 / 31) { range = 24 * 60 * 60; a *= 31; b *= 31; a += tm_hi.tm_mday - tptr->tm_mday; b += tptr->tm_mday - tm_lo.tm_mday; if (a + b <= 46000 / 24) { range = 60 * 60; a *= 24; b *= 24; a += tm_hi.tm_hour - tptr->tm_hour; b += tptr->tm_hour - tm_lo.tm_hour; if (a + b <= 46000 / 60) { range = 60; a *= 60; b *= 60; a += tm_hi.tm_min - tptr->tm_min; b += tptr->tm_min - tm_lo.tm_min; if (a + b <= 46000 / 60) { range = 1; a *= 60; b *= 60; a += tm_hi.tm_sec - tptr->tm_sec; b += tptr->tm_sec - tm_lo.tm_sec; } } } } } if (a <= 0) a = 1; if (b <= 0) b = 1; d = a + b; /* Although `/' and `%' may produce unexpected result with negative argument, it doesn't cause serious problem because there is a fail safe. */ guess = guess_lo / d * a + (guess_lo % d) * a / d + guess_hi / d * b + (guess_hi % d) * b / d; have_guess = 1; } if (guess <= guess_lo || guess_hi <= guess) { /* Precious guess is invalid. try binary search. */ guess = guess_lo / 2 + guess_hi / 2; if (guess <= guess_lo) guess = guess_lo + 1; else if (guess >= guess_hi) guess = guess_hi - 1; range = 0; } tm = (utc_p ? gmtime : localtime)(&guess); if (!tm) goto error; have_guess = 0; d = tmcmp(tptr, tm); if (d < 0) { guess_hi = guess; tm_hi = *tm; if (range) { guess = guess - range; range = 0; if (guess_lo < guess && guess < guess_hi) have_guess = 1; } } else if (d > 0) { guess_lo = guess; tm_lo = *tm; if (range) { guess = guess + range; range = 0; if (guess_lo < guess && guess < guess_hi) have_guess = 1; } } else { if (!utc_p) { /* If localtime is nonmonotonic, another result may exist. */ time_t guess2; if (find_dst) { guess2 = guess - 2 * 60 * 60; tm = localtime(&guess2); if (tm) { if (tptr->tm_hour != (tm->tm_hour + 2) % 24 || tptr->tm_min != tm->tm_min || tptr->tm_sec != tm->tm_sec) { guess2 -= (tm->tm_hour - tptr->tm_hour) * 60 * 60 + (tm->tm_min - tptr->tm_min) * 60 + (tm->tm_sec - tptr->tm_sec); if (tptr->tm_mday != tm->tm_mday) guess2 += 24 * 60 * 60; if (guess != guess2) { tm = localtime(&guess2); if (tmcmp(tptr, tm) == 0) { if (guess < guess2) return guess; else return guess2; } } } } } else { guess2 = guess + 2 * 60 * 60; tm = localtime(&guess2); if (tm) { if ((tptr->tm_hour + 2) % 24 != tm->tm_hour || tptr->tm_min != tm->tm_min || tptr->tm_sec != tm->tm_sec) { guess2 -= (tm->tm_hour - tptr->tm_hour) * 60 * 60 + (tm->tm_min - tptr->tm_min) * 60 + (tm->tm_sec - tptr->tm_sec); if (tptr->tm_mday != tm->tm_mday) guess2 -= 24 * 60 * 60; if (guess != guess2) { tm = localtime(&guess2); if (tmcmp(tptr, tm) == 0) { if (guess < guess2) return guess2; else return guess; } } } } } } return guess; } } /* Given argument has no corresponding time_t. Let's outerpolation. */ if (tm_lo.tm_year == tptr->tm_year && tm_lo.tm_mon == tptr->tm_mon) { return guess_lo + (tptr->tm_mday - tm_lo.tm_mday) * 24 * 60 * 60 + (tptr->tm_hour - tm_lo.tm_hour) * 60 * 60 + (tptr->tm_min - tm_lo.tm_min) * 60 + (tptr->tm_sec - tm_lo.tm_sec); } else if (tm_hi.tm_year == tptr->tm_year && tm_hi.tm_mon == tptr->tm_mon) { return guess_hi + (tptr->tm_mday - tm_hi.tm_mday) * 24 * 60 * 60 + (tptr->tm_hour - tm_hi.tm_hour) * 60 * 60 + (tptr->tm_min - tm_hi.tm_min) * 60 + (tptr->tm_sec - tm_hi.tm_sec); } out_of_range: rb_raise(rb_eArgError, "time out of range"); error: rb_raise(rb_eArgError, "gmtime/localtime error"); return 0; /* not reached */ } static time_t make_time_t(tptr, utc_p) struct tm *tptr; int utc_p; { time_t t; struct tm *tmp, buf; buf = *tptr; if (utc_p) { #if defined(HAVE_TIMEGM) if ((t = timegm(&buf)) != -1) return t; #ifdef NEGATIVE_TIME_T if ((tmp = gmtime(&t)) && tptr->tm_year == tmp->tm_year && tptr->tm_mon == tmp->tm_mon && tptr->tm_mday == tmp->tm_mday && tptr->tm_hour == tmp->tm_hour && tptr->tm_min == tmp->tm_min && tptr->tm_sec == tmp->tm_sec) return t; #endif #endif return search_time_t(&buf, utc_p); } else { #if defined(HAVE_MKTIME) if ((t = mktime(&buf)) != -1) return t; #ifdef NEGATIVE_TIME_T if ((tmp = localtime(&t)) && tptr->tm_year == tmp->tm_year && tptr->tm_mon == tmp->tm_mon && tptr->tm_mday == tmp->tm_mday && tptr->tm_hour == tmp->tm_hour && tptr->tm_min == tmp->tm_min && tptr->tm_sec == tmp->tm_sec) return t; #endif #endif return search_time_t(&buf, utc_p); } } static VALUE time_utc_or_local(argc, argv, utc_p, klass) int argc; VALUE *argv; int utc_p; VALUE klass; { struct tm tm; VALUE time; time_t usec; time_arg(argc, argv, &tm, &usec); time = time_new_internal(klass, make_time_t(&tm, utc_p), usec); if (utc_p) return time_gmtime(time); return time_localtime(time); } /* * call-seq: * Time.utc( year [, month, day, hour, min, sec, usec] ) => time * Time.utc( sec, min, hour, day, month, year, wday, yday, isdst, tz * ) => time * Time.gm( year [, month, day, hour, min, sec, usec] ) => time * Time.gm( sec, min, hour, day, month, year, wday, yday, isdst, tz * ) => time * * Creates a time based on given values, interpreted as UTC (GMT). The * year must be specified. Other values default to the minimum value * for that field (and may be nil or omitted). Months may * be specified by numbers from 1 to 12, or by the three-letter English * month names. Hours are specified on a 24-hour clock (0..23). Raises * an ArgumentError if any values are out of range. Will * also accept ten arguments in the order output by * Time#to_a. * * Time.utc(2000,"jan",1,20,15,1) #=> Sat Jan 01 20:15:01 UTC 2000 * Time.gm(2000,"jan",1,20,15,1) #=> Sat Jan 01 20:15:01 UTC 2000 */ static VALUE time_s_mkutc(argc, argv, klass) int argc; VALUE *argv; VALUE klass; { return time_utc_or_local(argc, argv, Qtrue, klass); } /* * call-seq: * Time.local( year [, month, day, hour, min, sec, usec] ) => time * Time.local( sec, min, hour, day, month, year, wday, yday, isdst, * tz ) => time * Time.mktime( year, month, day, hour, min, sec, usec ) => time * * Same as Time::gm, but interprets the values in the * local time zone. * * Time.local(2000,"jan",1,20,15,1) #=> Sat Jan 01 20:15:01 CST 2000 */ static VALUE time_s_mktime(argc, argv, klass) int argc; VALUE *argv; VALUE klass; { return time_utc_or_local(argc, argv, Qfalse, klass); } /* * call-seq: * time.to_i => int * time.tv_sec => int * * Returns the value of time as an integer number of seconds * since epoch. * * t = Time.now * "%10.5f" % t.to_f #=> "1049896564.17839" * t.to_i #=> 1049896564 */ static VALUE time_to_i(time) VALUE time; { struct time_object *tobj; GetTimeval(time, tobj); return LONG2NUM(tobj->tv.tv_sec); } /* * call-seq: * time.to_f => float * * Returns the value of time as a floating point number of * seconds since epoch. * * t = Time.now * "%10.5f" % t.to_f #=> "1049896564.13654" * t.to_i #=> 1049896564 */ static VALUE time_to_f(time) VALUE time; { struct time_object *tobj; GetTimeval(time, tobj); return rb_float_new((double)tobj->tv.tv_sec+(double)tobj->tv.tv_usec/1e6); } /* * call-seq: * time.usec => int * time.tv_usec => int * * Returns just the number of microseconds for time. * * t = Time.now #=> Wed Apr 09 08:56:04 CDT 2003 * "%10.6f" % t.to_f #=> "1049896564.259970" * t.usec #=> 259970 */ static VALUE time_usec(time) VALUE time; { struct time_object *tobj; GetTimeval(time, tobj); return LONG2NUM(tobj->tv.tv_usec); } /* * call-seq: * time <=> other_time => -1, 0, +1 * time <=> numeric => -1, 0, +1 * * Comparison---Compares time with other_time or with * numeric, which is the number of seconds (possibly * fractional) since epoch. * * t = Time.now #=> Wed Apr 09 08:56:03 CDT 2003 * t2 = t + 2592000 #=> Fri May 09 08:56:03 CDT 2003 * t <=> t2 #=> -1 * t2 <=> t #=> 1 * t <=> t #=> 0 */ static VALUE time_cmp(time1, time2) VALUE time1, time2; { struct time_object *tobj1, *tobj2; GetTimeval(time1, tobj1); if (TYPE(time2) == T_DATA && RDATA(time2)->dfree == time_free) { GetTimeval(time2, tobj2); if (tobj1->tv.tv_sec == tobj2->tv.tv_sec) { if (tobj1->tv.tv_usec == tobj2->tv.tv_usec) return INT2FIX(0); if (tobj1->tv.tv_usec > tobj2->tv.tv_usec) return INT2FIX(1); return INT2FIX(-1); } if (tobj1->tv.tv_sec > tobj2->tv.tv_sec) return INT2FIX(1); return INT2FIX(-1); } return Qnil; } /* * call-seq: * time.eql?(other_time) * * Return true if time and other_time are * both Time objects with the same seconds and fractional * seconds. */ static VALUE time_eql(time1, time2) VALUE time1, time2; { struct time_object *tobj1, *tobj2; GetTimeval(time1, tobj1); if (TYPE(time2) == T_DATA && RDATA(time2)->dfree == time_free) { GetTimeval(time2, tobj2); if (tobj1->tv.tv_sec == tobj2->tv.tv_sec) { if (tobj1->tv.tv_usec == tobj2->tv.tv_usec) return Qtrue; } } return Qfalse; } /* * call-seq: * time.utc? => true or false * time.gmt? => true or false * * Returns true if time represents a time in UTC * (GMT). * * t = Time.now #=> Wed Apr 09 08:56:04 CDT 2003 * t.utc? #=> false * t = Time.gm(2000,"jan",1,20,15,1) #=> Sat Jan 01 20:15:01 UTC 2000 * t.utc? #=> true * * t = Time.now #=> Wed Apr 09 08:56:03 CDT 2003 * t.gmt? #=> false * t = Time.gm(2000,1,1,20,15,1) #=> Sat Jan 01 20:15:01 UTC 2000 * t.gmt? #=> true */ static VALUE time_utc_p(time) VALUE time; { struct time_object *tobj; GetTimeval(time, tobj); if (tobj->gmt) return Qtrue; return Qfalse; } /* * call-seq: * time.hash => fixnum * * Return a hash code for this time object. */ static VALUE time_hash(time) VALUE time; { struct time_object *tobj; long hash; GetTimeval(time, tobj); hash = tobj->tv.tv_sec ^ tobj->tv.tv_usec; return LONG2FIX(hash); } /* :nodoc: */ static VALUE time_init_copy(copy, time) VALUE copy, time; { struct time_object *tobj, *tcopy; if (copy == time) return copy; time_modify(copy); if (TYPE(time) != T_DATA || RDATA(time)->dfree != time_free) { rb_raise(rb_eTypeError, "wrong argument type"); } GetTimeval(time, tobj); GetTimeval(copy, tcopy); MEMCPY(tcopy, tobj, struct time_object, 1); return copy; } static VALUE time_dup(time) VALUE time; { VALUE dup = time_s_alloc(CLASS_OF(time)); time_init_copy(dup, time); return dup; } /* * call-seq: * time.localtime => time * * Converts time to local time (using the local time zone in * effect for this process) modifying the receiver. * * t = Time.gm(2000, "jan", 1, 20, 15, 1) * t.gmt? #=> true * t.localtime #=> Sat Jan 01 14:15:01 CST 2000 * t.gmt? #=> false */ static VALUE time_localtime(time) VALUE time; { struct time_object *tobj; struct tm *tm_tmp; time_t t; GetTimeval(time, tobj); if (!tobj->gmt) { if (tobj->tm_got) return time; } else { time_modify(time); } t = tobj->tv.tv_sec; tm_tmp = localtime(&t); if (!tm_tmp) rb_raise(rb_eArgError, "localtime error"); tobj->tm = *tm_tmp; tobj->tm_got = 1; tobj->gmt = 0; return time; } /* * call-seq: * time.gmtime => time * time.utc => time * * Converts time to UTC (GMT), modifying the receiver. * * t = Time.now #=> Wed Apr 09 08:56:03 CDT 2003 * t.gmt? #=> false * t.gmtime #=> Wed Apr 09 13:56:03 UTC 2003 * t.gmt? #=> true * * t = Time.now #=> Wed Apr 09 08:56:04 CDT 2003 * t.utc? #=> false * t.utc #=> Wed Apr 09 13:56:04 UTC 2003 * t.utc? #=> true */ static VALUE time_gmtime(time) VALUE time; { struct time_object *tobj; struct tm *tm_tmp; time_t t; GetTimeval(time, tobj); if (tobj->gmt) { if (tobj->tm_got) return time; } else { time_modify(time); } t = tobj->tv.tv_sec; tm_tmp = gmtime(&t); if (!tm_tmp) rb_raise(rb_eArgError, "gmtime error"); tobj->tm = *tm_tmp; tobj->tm_got = 1; tobj->gmt = 1; return time; } /* * call-seq: * time.getlocal => new_time * * Returns a new new_time object representing time in * local time (using the local time zone in effect for this process). * * t = Time.gm(2000,1,1,20,15,1) #=> Sat Jan 01 20:15:01 UTC 2000 * t.gmt? #=> true * l = t.getlocal #=> Sat Jan 01 14:15:01 CST 2000 * l.gmt? #=> false * t == l #=> true */ static VALUE time_getlocaltime(time) VALUE time; { return time_localtime(time_dup(time)); } /* * call-seq: * time.getgm => new_time * time.getutc => new_time * * Returns a new new_time object representing time in * UTC. * * t = Time.local(2000,1,1,20,15,1) #=> Sat Jan 01 20:15:01 CST 2000 * t.gmt? #=> false * y = t.getgm #=> Sun Jan 02 02:15:01 UTC 2000 * y.gmt? #=> true * t == y #=> true */ static VALUE time_getgmtime(time) VALUE time; { return time_gmtime(time_dup(time)); } static VALUE time_get_tm(time, gmt) VALUE time; int gmt; { if (gmt) return time_gmtime(time); return time_localtime(time); } /* * call-seq: * time.asctime => string * time.ctime => string * * Returns a canonical string representation of time. * * Time.now.asctime #=> "Wed Apr 9 08:56:03 2003" */ static VALUE time_asctime(time) VALUE time; { struct time_object *tobj; char *s; GetTimeval(time, tobj); if (tobj->tm_got == 0) { time_get_tm(time, tobj->gmt); } s = asctime(&tobj->tm); if (s[24] == '\n') s[24] = '\0'; return rb_str_new2(s); } /* * call-seq: * time.inspect => string * time.to_s => string * * Returns a string representing time. Equivalent to calling * Time#strftime with a format string of ``%a * %b %d %H:%M:%S * %Z %Y''. * * Time.now.to_s #=> "Wed Apr 09 08:56:04 CDT 2003" */ static VALUE time_to_s(time) VALUE time; { struct time_object *tobj; char buf[128]; int len; GetTimeval(time, tobj); if (tobj->tm_got == 0) { time_get_tm(time, tobj->gmt); } if (tobj->gmt == 1) { len = strftime(buf, 128, "%a %b %d %H:%M:%S UTC %Y", &tobj->tm); } else { time_t off; char buf2[32]; char sign = '+'; #if defined(HAVE_STRUCT_TM_TM_GMTOFF) off = tobj->tm.tm_gmtoff; #else VALUE tmp = time_utc_offset(time); off = NUM2INT(tmp); #endif if (off < 0) { sign = '-'; off = -off; } sprintf(buf2, "%%a %%b %%d %%H:%%M:%%S %c%02d%02d %%Y", sign, (int)(off/3600), (int)(off%3600/60)); len = strftime(buf, 128, buf2, &tobj->tm); } return rb_str_new(buf, len); } static VALUE time_add(tobj, offset, sign) struct time_object *tobj; VALUE offset; int sign; { double v = NUM2DBL(offset); double f, d; unsigned_time_t sec_off; time_t usec_off, sec, usec; VALUE result; if (v < 0) { v = -v; sign = -sign; } d = modf(v, &f); sec_off = (unsigned_time_t)f; if (f != (double)sec_off) rb_raise(rb_eRangeError, "time %s %f out of Time range", sign < 0 ? "-" : "+", v); usec_off = (time_t)(d*1e6+0.5); if (sign < 0) { sec = tobj->tv.tv_sec - sec_off; usec = tobj->tv.tv_usec - usec_off; if (sec > tobj->tv.tv_sec) rb_raise(rb_eRangeError, "time - %f out of Time range", v); } else { sec = tobj->tv.tv_sec + sec_off; usec = tobj->tv.tv_usec + usec_off; if (sec < tobj->tv.tv_sec) rb_raise(rb_eRangeError, "time + %f out of Time range", v); } result = rb_time_new(sec, usec); if (tobj->gmt) { GetTimeval(result, tobj); tobj->gmt = 1; } return result; } /* * call-seq: * time + numeric => time * * Addition---Adds some number of seconds (possibly fractional) to * time and returns that value as a new time. * * t = Time.now #=> Wed Apr 09 08:56:03 CDT 2003 * t + (60 * 60 * 24) #=> Thu Apr 10 08:56:03 CDT 2003 */ static VALUE time_plus(time1, time2) VALUE time1, time2; { struct time_object *tobj; GetTimeval(time1, tobj); if (TYPE(time2) == T_DATA && RDATA(time2)->dfree == time_free) { rb_raise(rb_eTypeError, "time + time?"); } return time_add(tobj, time2, 1); } /* * call-seq: * time - other_time => float * time - numeric => time * * Difference---Returns a new time that represents the difference * between two times, or subtracts the given number of seconds in * numeric from time. * * t = Time.now #=> Wed Apr 09 08:56:03 CDT 2003 * t2 = t + 2592000 #=> Fri May 09 08:56:03 CDT 2003 * t2 - t #=> 2592000.0 * t2 - 2592000 #=> Wed Apr 09 08:56:03 CDT 2003 */ static VALUE time_minus(time1, time2) VALUE time1, time2; { struct time_object *tobj; GetTimeval(time1, tobj); if (TYPE(time2) == T_DATA && RDATA(time2)->dfree == time_free) { struct time_object *tobj2; double f; GetTimeval(time2, tobj2); f = (double)tobj->tv.tv_sec - (double)tobj2->tv.tv_sec; f += ((double)tobj->tv.tv_usec - (double)tobj2->tv.tv_usec)*1e-6; /* XXX: should check float overflow on 64bit time_t platforms */ return rb_float_new(f); } return time_add(tobj, time2, -1); } /* * call-seq: * time.succ => new_time * * Return a new time object, one second later than time. */ static VALUE time_succ(time) VALUE time; { struct time_object *tobj; int gmt; GetTimeval(time, tobj); gmt = tobj->gmt; time = rb_time_new(tobj->tv.tv_sec + 1, tobj->tv.tv_usec); GetTimeval(time, tobj); tobj->gmt = gmt; return time; } /* * call-seq: * time.sec => fixnum * * Returns the second of the minute (0..60)[Yes, seconds really can * range from zero to 60. This allows the system to inject leap seconds * every now and then to correct for the fact that years are not really * a convenient number of hours long.] for time. * * t = Time.now #=> Wed Apr 09 08:56:04 CDT 2003 * t.sec #=> 4 */ static VALUE time_sec(time) VALUE time; { struct time_object *tobj; GetTimeval(time, tobj); if (tobj->tm_got == 0) { time_get_tm(time, tobj->gmt); } return INT2FIX(tobj->tm.tm_sec); } /* * call-seq: * time.min => fixnum * * Returns the minute of the hour (0..59) for time. * * t = Time.now #=> Wed Apr 09 08:56:03 CDT 2003 * t.min #=> 56 */ static VALUE time_min(time) VALUE time; { struct time_object *tobj; GetTimeval(time, tobj); if (tobj->tm_got == 0) { time_get_tm(time, tobj->gmt); } return INT2FIX(tobj->tm.tm_min); } /* * call-seq: * time.hour => fixnum * * Returns the hour of the day (0..23) for time. * * t = Time.now #=> Wed Apr 09 08:56:03 CDT 2003 * t.hour #=> 8 */ static VALUE time_hour(time) VALUE time; { struct time_object *tobj; GetTimeval(time, tobj); if (tobj->tm_got == 0) { time_get_tm(time, tobj->gmt); } return INT2FIX(tobj->tm.tm_hour); } /* * call-seq: * time.day => fixnum * time.mday => fixnum * * Returns the day of the month (1..n) for time. * * t = Time.now #=> Wed Apr 09 08:56:03 CDT 2003 * t.day #=> 9 * t.mday #=> 9 */ static VALUE time_mday(time) VALUE time; { struct time_object *tobj; GetTimeval(time, tobj); if (tobj->tm_got == 0) { time_get_tm(time, tobj->gmt); } return INT2FIX(tobj->tm.tm_mday); } /* * call-seq: * time.mon => fixnum * time.month => fixnum * * Returns the month of the year (1..12) for time. * * t = Time.now #=> Wed Apr 09 08:56:03 CDT 2003 * t.mon #=> 4 * t.month #=> 4 */ static VALUE time_mon(time) VALUE time; { struct time_object *tobj; GetTimeval(time, tobj); if (tobj->tm_got == 0) { time_get_tm(time, tobj->gmt); } return INT2FIX(tobj->tm.tm_mon+1); } /* * call-seq: * time.year => fixnum * * Returns the year for time (including the century). * * t = Time.now #=> Wed Apr 09 08:56:04 CDT 2003 * t.year #=> 2003 */ static VALUE time_year(time) VALUE time; { struct time_object *tobj; GetTimeval(time, tobj); if (tobj->tm_got == 0) { time_get_tm(time, tobj->gmt); } return LONG2NUM((long)tobj->tm.tm_year+1900); } /* * call-seq: * time.wday => fixnum * * Returns an integer representing the day of the week, 0..6, with * Sunday == 0. * * t = Time.now #=> Wed Apr 09 08:56:04 CDT 2003 * t.wday #=> 3 */ static VALUE time_wday(time) VALUE time; { struct time_object *tobj; GetTimeval(time, tobj); if (tobj->tm_got == 0) { time_get_tm(time, tobj->gmt); } return INT2FIX(tobj->tm.tm_wday); } /* * call-seq: * time.yday => fixnum * * Returns an integer representing the day of the year, 1..366. * * t = Time.now #=> Wed Apr 09 08:56:04 CDT 2003 * t.yday #=> 99 */ static VALUE time_yday(time) VALUE time; { struct time_object *tobj; GetTimeval(time, tobj); if (tobj->tm_got == 0) { time_get_tm(time, tobj->gmt); } return INT2FIX(tobj->tm.tm_yday+1); } /* * call-seq: * time.isdst => true or false * time.dst? => true or false * * Returns true if time occurs during Daylight * Saving Time in its time zone. * * Time.local(2000, 7, 1).isdst #=> true * Time.local(2000, 1, 1).isdst #=> false * Time.local(2000, 7, 1).dst? #=> true * Time.local(2000, 1, 1).dst? #=> false */ static VALUE time_isdst(time) VALUE time; { struct time_object *tobj; GetTimeval(time, tobj); if (tobj->tm_got == 0) { time_get_tm(time, tobj->gmt); } return tobj->tm.tm_isdst?Qtrue:Qfalse; } /* * call-seq: * time.zone => string * * Returns the name of the time zone used for time. As of Ruby * 1.8, returns ``UTC'' rather than ``GMT'' for UTC times. * * t = Time.gm(2000, "jan", 1, 20, 15, 1) * t.zone #=> "UTC" * t = Time.local(2000, "jan", 1, 20, 15, 1) * t.zone #=> "CST" */ static VALUE time_zone(time) VALUE time; { struct time_object *tobj; #if !defined(HAVE_TM_ZONE) && (!defined(HAVE_TZNAME) || !defined(HAVE_DAYLIGHT)) char buf[64]; int len; #endif GetTimeval(time, tobj); if (tobj->tm_got == 0) { time_get_tm(time, tobj->gmt); } if (tobj->gmt == 1) { return rb_str_new2("UTC"); } #if defined(HAVE_TM_ZONE) return rb_str_new2(tobj->tm.tm_zone); #elif defined(HAVE_TZNAME) && defined(HAVE_DAYLIGHT) return rb_str_new2(tzname[daylight && tobj->tm.tm_isdst]); #else len = strftime(buf, 64, "%Z", &tobj->tm); return rb_str_new(buf, len); #endif } /* * call-seq: * time.gmt_offset => fixnum * time.gmtoff => fixnum * time.utc_offset => fixnum * * Returns the offset in seconds between the timezone of time * and UTC. * * t = Time.gm(2000,1,1,20,15,1) #=> Sat Jan 01 20:15:01 UTC 2000 * t.gmt_offset #=> 0 * l = t.getlocal #=> Sat Jan 01 14:15:01 CST 2000 * l.gmt_offset #=> -21600 */ static VALUE time_utc_offset(time) VALUE time; { struct time_object *tobj; GetTimeval(time, tobj); if (tobj->tm_got == 0) { time_get_tm(time, tobj->gmt); } if (tobj->gmt == 1) { return INT2FIX(0); } else { #if defined(HAVE_STRUCT_TM_TM_GMTOFF) return INT2NUM(tobj->tm.tm_gmtoff); #else struct tm *u, *l; time_t t; long off; l = &tobj->tm; t = tobj->tv.tv_sec; u = gmtime(&t); if (!u) rb_raise(rb_eArgError, "gmtime error"); if (l->tm_year != u->tm_year) off = l->tm_year < u->tm_year ? -1 : 1; else if (l->tm_mon != u->tm_mon) off = l->tm_mon < u->tm_mon ? -1 : 1; else if (l->tm_mday != u->tm_mday) off = l->tm_mday < u->tm_mday ? -1 : 1; else off = 0; off = off * 24 + l->tm_hour - u->tm_hour; off = off * 60 + l->tm_min - u->tm_min; off = off * 60 + l->tm_sec - u->tm_sec; return LONG2FIX(off); #endif } } /* * call-seq: * time.to_a => array * * Returns a ten-element array of values for time: * {[ sec, min, hour, day, month, year, wday, yday, isdst, zone * ]}. See the individual methods for an explanation of the * valid ranges of each value. The ten elements can be passed directly * to Time::utc or Time::local to create a * new Time. * * now = Time.now #=> Wed Apr 09 08:56:04 CDT 2003 * t = now.to_a #=> [4, 56, 8, 9, 4, 2003, 3, 99, true, "CDT"] */ static VALUE time_to_a(time) VALUE time; { struct time_object *tobj; GetTimeval(time, tobj); if (tobj->tm_got == 0) { time_get_tm(time, tobj->gmt); } return rb_ary_new3(10, INT2FIX(tobj->tm.tm_sec), INT2FIX(tobj->tm.tm_min), INT2FIX(tobj->tm.tm_hour), INT2FIX(tobj->tm.tm_mday), INT2FIX(tobj->tm.tm_mon+1), LONG2NUM((long)tobj->tm.tm_year+1900), INT2FIX(tobj->tm.tm_wday), INT2FIX(tobj->tm.tm_yday+1), tobj->tm.tm_isdst?Qtrue:Qfalse, time_zone(time)); } #define SMALLBUF 100 static int rb_strftime(buf, format, time) char **buf; const char *format; struct tm *time; { int size, len, flen; (*buf)[0] = '\0'; flen = strlen(format); if (flen == 0) { return 0; } errno = 0; len = strftime(*buf, SMALLBUF, format, time); if (len != 0 || (**buf == '\0' && errno != ERANGE)) return len; for (size=1024; ; size*=2) { *buf = xmalloc(size); (*buf)[0] = '\0'; len = strftime(*buf, size, format, time); /* * buflen can be zero EITHER because there's not enough * room in the string, or because the control command * goes to the empty string. Make a reasonable guess that * if the buffer is 1024 times bigger than the length of the * format string, it's not failing for lack of room. */ if (len > 0 || size >= 1024 * flen) return len; free(*buf); } /* not reached */ } /* * call-seq: * time.strftime( string ) => string * * Formats time according to the directives in the given format * string. Any text not listed as a directive will be passed through * to the output string. * * Format meaning: * %a - The abbreviated weekday name (``Sun'') * %A - The full weekday name (``Sunday'') * %b - The abbreviated month name (``Jan'') * %B - The full month name (``January'') * %c - The preferred local date and time representation * %d - Day of the month (01..31) * %H - Hour of the day, 24-hour clock (00..23) * %I - Hour of the day, 12-hour clock (01..12) * %j - Day of the year (001..366) * %m - Month of the year (01..12) * %M - Minute of the hour (00..59) * %p - Meridian indicator (``AM'' or ``PM'') * %S - Second of the minute (00..60) * %U - Week number of the current year, * starting with the first Sunday as the first * day of the first week (00..53) * %W - Week number of the current year, * starting with the first Monday as the first * day of the first week (00..53) * %w - Day of the week (Sunday is 0, 0..6) * %x - Preferred representation for the date alone, no time * %X - Preferred representation for the time alone, no date * %y - Year without a century (00..99) * %Y - Year with century * %Z - Time zone name * %% - Literal ``%'' character * * t = Time.now * t.strftime("Printed on %m/%d/%Y") #=> "Printed on 04/09/2003" * t.strftime("at %I:%M%p") #=> "at 08:56AM" */ static VALUE time_strftime(time, format) VALUE time, format; { struct time_object *tobj; char buffer[SMALLBUF], *buf = buffer; const char *fmt; long len; VALUE str; GetTimeval(time, tobj); if (tobj->tm_got == 0) { time_get_tm(time, tobj->gmt); } StringValue(format); format = rb_str_new4(format); fmt = RSTRING(format)->ptr; len = RSTRING(format)->len; if (len == 0) { rb_warning("strftime called with empty format string"); } else if (strlen(fmt) < len) { /* Ruby string may contain \0's. */ const char *p = fmt, *pe = fmt + len; str = rb_str_new(0, 0); while (p < pe) { len = rb_strftime(&buf, p, &tobj->tm); rb_str_cat(str, buf, len); p += strlen(p); if (buf != buffer) { free(buf); buf = buffer; } for (fmt = p; p < pe && !*p; ++p); if (p > fmt) rb_str_cat(str, fmt, p - fmt); } return str; } else { len = rb_strftime(&buf, RSTRING(format)->ptr, &tobj->tm); } str = rb_str_new(buf, len); if (buf != buffer) free(buf); return str; } /* * call-seq: * Time.times => struct_tms * * Deprecated in favor of Process::times */ static VALUE time_s_times(obj) VALUE obj; { rb_warn("obsolete method Time::times; use Process::times"); return rb_proc_times(obj); } /* * undocumented */ static VALUE time_mdump(time) VALUE time; { struct time_object *tobj; struct tm *tm; unsigned long p, s; char buf[8]; time_t t; int i; GetTimeval(time, tobj); t = tobj->tv.tv_sec; tm = gmtime(&t); if ((tm->tm_year & 0xffff) != tm->tm_year) rb_raise(rb_eArgError, "year too big to marshal"); p = 0x1UL << 31 | /* 1 */ tobj->gmt << 30 | /* 1 */ tm->tm_year << 14 | /* 16 */ tm->tm_mon << 10 | /* 4 */ tm->tm_mday << 5 | /* 5 */ tm->tm_hour; /* 5 */ s = tm->tm_min << 26 | /* 6 */ tm->tm_sec << 20 | /* 6 */ tobj->tv.tv_usec; /* 20 */ for (i=0; i<4; i++) { buf[i] = p & 0xff; p = RSHIFT(p, 8); } for (i=4; i<8; i++) { buf[i] = s & 0xff; s = RSHIFT(s, 8); } return rb_str_new(buf, 8); } /* * call-seq: * time._dump => string * * Dump _time_ for marshaling. */ static VALUE time_dump(argc, argv, time) int argc; VALUE *argv; VALUE time; { VALUE str; rb_scan_args(argc, argv, "01", 0); str = time_mdump(time); if (FL_TEST(time, FL_EXIVAR)) { rb_copy_generic_ivar(str, time); FL_SET(str, FL_EXIVAR); } return str; } /* * undocumented */ static VALUE time_mload(time, str) VALUE time, str; { struct time_object *tobj; unsigned long p, s; time_t sec, usec; unsigned char *buf; struct tm tm; int i, gmt; time_modify(time); StringValue(str); buf = (unsigned char *)RSTRING(str)->ptr; if (RSTRING(str)->len != 8) { rb_raise(rb_eTypeError, "marshaled time format differ"); } p = s = 0; for (i=0; i<4; i++) { p |= buf[i]<<(8*i); } for (i=4; i<8; i++) { s |= buf[i]<<(8*(i-4)); } if ((p & (1UL<<31)) == 0) { sec = p; usec = s; } else { p &= ~(1UL<<31); gmt = (p >> 30) & 0x1; tm.tm_year = (p >> 14) & 0xffff; tm.tm_mon = (p >> 10) & 0xf; tm.tm_mday = (p >> 5) & 0x1f; tm.tm_hour = p & 0x1f; tm.tm_min = (s >> 26) & 0x3f; tm.tm_sec = (s >> 20) & 0x3f; tm.tm_isdst = 0; sec = make_time_t(&tm, Qtrue); usec = (time_t)(s & 0xfffff); } time_overflow_p(&sec, &usec); GetTimeval(time, tobj); tobj->tm_got = 0; tobj->gmt = gmt; tobj->tv.tv_sec = sec; tobj->tv.tv_usec = usec; return time; } /* * call-seq: * Time._load(string) => time * * Unmarshal a dumped +Time+ object. */ static VALUE time_load(klass, str) VALUE klass, str; { VALUE time = time_s_alloc(klass); if (FL_TEST(str, FL_EXIVAR)) { rb_copy_generic_ivar(time, str); FL_SET(time, FL_EXIVAR); } time_mload(time, str); return time; } /* * Time is an abstraction of dates and times. Time is * stored internally as the number of seconds and microseconds since * the epoch, January 1, 1970 00:00 UTC. On some operating * systems, this offset is allowed to be negative. Also see the * library modules Date and ParseDate. The * Time class treats GMT (Greenwich Mean Time) and UTC * (Coordinated Universal Time)[Yes, UTC really does stand for * Coordinated Universal Time. There was a committee involved.] * as equivalent. GMT is the older way of referring to these * baseline times but persists in the names of calls on Posix * systems. * * All times are stored with some number of microseconds. Be aware of * this fact when comparing times with each other---times that are * apparently equal when displayed may be different when compared. */ void Init_Time() { rb_cTime = rb_define_class("Time", rb_cObject); rb_include_module(rb_cTime, rb_mComparable); rb_define_alloc_func(rb_cTime, time_s_alloc); rb_define_singleton_method(rb_cTime, "now", rb_class_new_instance, -1); rb_define_singleton_method(rb_cTime, "at", time_s_at, -1); rb_define_singleton_method(rb_cTime, "utc", time_s_mkutc, -1); rb_define_singleton_method(rb_cTime, "gm", time_s_mkutc, -1); rb_define_singleton_method(rb_cTime, "local", time_s_mktime, -1); rb_define_singleton_method(rb_cTime, "mktime", time_s_mktime, -1); rb_define_singleton_method(rb_cTime, "times", time_s_times, 0); rb_define_method(rb_cTime, "to_i", time_to_i, 0); rb_define_method(rb_cTime, "to_f", time_to_f, 0); rb_define_method(rb_cTime, "<=>", time_cmp, 1); rb_define_method(rb_cTime, "eql?", time_eql, 1); rb_define_method(rb_cTime, "hash", time_hash, 0); rb_define_method(rb_cTime, "initialize", time_init, 0); rb_define_method(rb_cTime, "initialize_copy", time_init_copy, 1); rb_define_method(rb_cTime, "localtime", time_localtime, 0); rb_define_method(rb_cTime, "gmtime", time_gmtime, 0); rb_define_method(rb_cTime, "utc", time_gmtime, 0); rb_define_method(rb_cTime, "getlocal", time_getlocaltime, 0); rb_define_method(rb_cTime, "getgm", time_getgmtime, 0); rb_define_method(rb_cTime, "getutc", time_getgmtime, 0); rb_define_method(rb_cTime, "ctime", time_asctime, 0); rb_define_method(rb_cTime, "asctime", time_asctime, 0); rb_define_method(rb_cTime, "to_s", time_to_s, 0); rb_define_method(rb_cTime, "inspect", time_to_s, 0); rb_define_method(rb_cTime, "to_a", time_to_a, 0); rb_define_method(rb_cTime, "+", time_plus, 1); rb_define_method(rb_cTime, "-", time_minus, 1); rb_define_method(rb_cTime, "succ", time_succ, 0); rb_define_method(rb_cTime, "sec", time_sec, 0); rb_define_method(rb_cTime, "min", time_min, 0); rb_define_method(rb_cTime, "hour", time_hour, 0); rb_define_method(rb_cTime, "mday", time_mday, 0); rb_define_method(rb_cTime, "day", time_mday, 0); rb_define_method(rb_cTime, "mon", time_mon, 0); rb_define_method(rb_cTime, "month", time_mon, 0); rb_define_method(rb_cTime, "year", time_year, 0); rb_define_method(rb_cTime, "wday", time_wday, 0); rb_define_method(rb_cTime, "yday", time_yday, 0); rb_define_method(rb_cTime, "isdst", time_isdst, 0); rb_define_method(rb_cTime, "dst?", time_isdst, 0); rb_define_method(rb_cTime, "zone", time_zone, 0); rb_define_method(rb_cTime, "gmtoff", time_utc_offset, 0); rb_define_method(rb_cTime, "gmt_offset", time_utc_offset, 0); rb_define_method(rb_cTime, "utc_offset", time_utc_offset, 0); rb_define_method(rb_cTime, "utc?", time_utc_p, 0); rb_define_method(rb_cTime, "gmt?", time_utc_p, 0); rb_define_method(rb_cTime, "tv_sec", time_to_i, 0); rb_define_method(rb_cTime, "tv_usec", time_usec, 0); rb_define_method(rb_cTime, "usec", time_usec, 0); rb_define_method(rb_cTime, "strftime", time_strftime, 1); /* methods for marshaling */ rb_define_method(rb_cTime, "_dump", time_dump, -1); rb_define_singleton_method(rb_cTime, "_load", time_load, 1); #if 0 /* Time will support marshal_dump and marshal_load in the future (1.9 maybe) */ rb_define_method(rb_cTime, "marshal_dump", time_mdump, 0); rb_define_method(rb_cTime, "marshal_load", time_mload, 1); #endif } /********************************************************************** util.c - $Author: wyhaines $ $Date: 2009-08-19 15:53:23 +0200 (Wed, 19 Aug 2009) $ created at: Fri Mar 10 17:22:34 JST 1995 Copyright (C) 1993-2003 Yukihiro Matsumoto **********************************************************************/ #include "ruby.h" #include #include #include #include #include #ifdef _WIN32 #include "missing/file.h" #endif #include "util.h" #ifndef HAVE_STRING_H char *strchr _((char*,char)); #endif unsigned long scan_oct(start, len, retlen) const char *start; int len; int *retlen; { register const char *s = start; register unsigned long retval = 0; while (len-- && *s >= '0' && *s <= '7') { retval <<= 3; retval |= *s++ - '0'; } *retlen = s - start; return retval; } unsigned long scan_hex(start, len, retlen) const char *start; int len; int *retlen; { static char hexdigit[] = "0123456789abcdef0123456789ABCDEF"; register const char *s = start; register unsigned long retval = 0; char *tmp; while (len-- && *s && (tmp = strchr(hexdigit, *s))) { retval <<= 4; retval |= (tmp - hexdigit) & 15; s++; } *retlen = s - start; return retval; } #include #include #ifdef HAVE_UNISTD_H #include #endif #if defined(HAVE_FCNTL_H) #include #endif #ifndef S_ISDIR # define S_ISDIR(m) ((m & S_IFMT) == S_IFDIR) #endif #if defined(MSDOS) || defined(__CYGWIN32__) || defined(_WIN32) /* * Copyright (c) 1993, Intergraph Corporation * * You may distribute under the terms of either the GNU General Public * License or the Artistic License, as specified in the perl README file. * * Various Unix compatibility functions and NT specific functions. * * Some of this code was derived from the MSDOS port(s) and the OS/2 port. * */ /* * Suffix appending for in-place editing under MS-DOS and OS/2 (and now NT!). * * Here are the rules: * * Style 0: Append the suffix exactly as standard perl would do it. * If the filesystem groks it, use it. (HPFS will always * grok it. So will NTFS. FAT will rarely accept it.) * * Style 1: The suffix begins with a '.'. The extension is replaced. * If the name matches the original name, use the fallback method. * * Style 2: The suffix is a single character, not a '.'. Try to add the * suffix to the following places, using the first one that works. * [1] Append to extension. * [2] Append to filename, * [3] Replace end of extension, * [4] Replace end of filename. * If the name matches the original name, use the fallback method. * * Style 3: Any other case: Ignore the suffix completely and use the * fallback method. * * Fallback method: Change the extension to ".$$$". If that matches the * original name, then change the extension to ".~~~". * * If filename is more than 1000 characters long, we die a horrible * death. Sorry. * * The filename restriction is a cheat so that we can use buf[] to store * assorted temporary goo. * * Examples, assuming style 0 failed. * * suffix = ".bak" (style 1) * foo.bar => foo.bak * foo.bak => foo.$$$ (fallback) * foo.$$$ => foo.~~~ (fallback) * makefile => makefile.bak * * suffix = "~" (style 2) * foo.c => foo.c~ * foo.c~ => foo.c~~ * foo.c~~ => foo~.c~~ * foo~.c~~ => foo~~.c~~ * foo~~~~~.c~~ => foo~~~~~.$$$ (fallback) * * foo.pas => foo~.pas * makefile => makefile.~ * longname.fil => longname.fi~ * longname.fi~ => longnam~.fi~ * longnam~.fi~ => longnam~.$$$ * */ static int valid_filename(char *s); static char suffix1[] = ".$$$"; static char suffix2[] = ".~~~"; #define ext (&buf[1000]) #define strEQ(s1,s2) (strcmp(s1,s2) == 0) void ruby_add_suffix(str, suffix) VALUE str; char *suffix; { int baselen; int extlen = strlen(suffix); char *s, *t, *p; long slen; char buf[1024]; if (RSTRING(str)->len > 1000) rb_fatal("Cannot do inplace edit on long filename (%ld characters)", RSTRING(str)->len); #if defined(DJGPP) || defined(__CYGWIN32__) || defined(_WIN32) /* Style 0 */ slen = RSTRING(str)->len; rb_str_cat(str, suffix, extlen); #if defined(DJGPP) if (_USE_LFN) return; #else if (valid_filename(RSTRING(str)->ptr)) return; #endif /* Fooey, style 0 failed. Fix str before continuing. */ RSTRING(str)->ptr[RSTRING(str)->len = slen] = '\0'; #endif slen = extlen; t = buf; baselen = 0; s = RSTRING(str)->ptr; while ((*t = *s) && *s != '.') { baselen++; if (*s == '\\' || *s == '/') baselen = 0; s++; t++; } p = t; t = ext; extlen = 0; while (*t++ = *s++) extlen++; if (extlen == 0) { ext[0] = '.'; ext[1] = 0; extlen++; } if (*suffix == '.') { /* Style 1 */ if (strEQ(ext, suffix)) goto fallback; strcpy(p, suffix); } else if (suffix[1] == '\0') { /* Style 2 */ if (extlen < 4) { ext[extlen] = *suffix; ext[++extlen] = '\0'; } else if (baselen < 8) { *p++ = *suffix; } else if (ext[3] != *suffix) { ext[3] = *suffix; } else if (buf[7] != *suffix) { buf[7] = *suffix; } else goto fallback; strcpy(p, ext); } else { /* Style 3: Panic */ fallback: (void)memcpy(p, strEQ(ext, suffix1) ? suffix2 : suffix1, 5); } rb_str_resize(str, strlen(buf)); memcpy(RSTRING(str)->ptr, buf, RSTRING(str)->len); } #if defined(__CYGWIN32__) || defined(_WIN32) static int valid_filename(char *s) { int fd; /* // if the file exists, then it's a valid filename! */ if (_access(s, 0) == 0) { return 1; } /* // It doesn't exist, so see if we can open it. */ if ((fd = _open(s, O_CREAT, 0666)) >= 0) { _close(fd); _unlink(s); /* don't leave it laying around */ return 1; } return 0; } #endif #endif #if defined __DJGPP__ #include static char dbcs_table[256]; int make_dbcs_table() { __dpmi_regs r; struct { unsigned char start; unsigned char end; } vec; int offset; memset(&r, 0, sizeof(r)); r.x.ax = 0x6300; __dpmi_int(0x21, &r); offset = r.x.ds * 16 + r.x.si; for (;;) { int i; dosmemget(offset, sizeof vec, &vec); if (!vec.start && !vec.end) break; for (i = vec.start; i <= vec.end; i++) dbcs_table[i] = 1; offset += 2; } } int mblen(const char *s, size_t n) { static int need_init = 1; if (need_init) { make_dbcs_table(); need_init = 0; } if (s) { if (n == 0 || *s == 0) return 0; else if (!s[1]) return 1; return dbcs_table[(unsigned char)*s] + 1; } else return 1; } struct PathList { struct PathList *next; char *path; }; struct PathInfo { struct PathList *head; int count; }; static int push_element(const char *path, VALUE vinfo) { struct PathList *p; struct PathInfo *info = (struct PathInfo *)vinfo; p = ALLOC(struct PathList); MEMZERO(p, struct PathList, 1); p->path = ruby_strdup(path); p->next = info->head; info->head = p; info->count++; return 0; } #include int __opendir_flags = __OPENDIR_PRESERVE_CASE; char ** __crt0_glob_function(char *path) { int len = strlen(path); int i; char **rv; char path_buffer[PATH_MAX]; char *buf = path_buffer; char *p; struct PathInfo info; struct PathList *plist; if (PATH_MAX <= len) buf = ruby_xmalloc(len + 1); strncpy(buf, path, len); buf[len] = '\0'; for (p = buf; *p; p += mblen(p, MB_CUR_MAX)) if (*p == '\\') *p = '/'; info.count = 0; info.head = 0; ruby_glob(buf, 0, push_element, (VALUE)&info); if (buf != path_buffer) ruby_xfree(buf); if (info.count == 0) return 0; rv = ruby_xmalloc((info.count + 1) * sizeof (char *)); plist = info.head; i = 0; while (plist) { struct PathList *cur; rv[i] = plist->path; cur = plist; plist = plist->next; ruby_xfree(cur); i++; } rv[i] = 0; return rv; } #endif /* mm.c */ #define A ((int*)a) #define B ((int*)b) #define C ((int*)c) #define D ((int*)d) #define mmprepare(base, size) do {\ if (((long)base & (0x3)) == 0)\ if (size >= 16) mmkind = 1;\ else mmkind = 0;\ else mmkind = -1;\ high = (size & (~0xf));\ low = (size & 0x0c);\ } while (0)\ #define mmarg mmkind, size, high, low static void mmswap_(a, b, mmarg) register char *a, *b; int mmarg; { register int s; if (a == b) return; if (mmkind >= 0) { if (mmkind > 0) { register char *t = a + high; do { s = A[0]; A[0] = B[0]; B[0] = s; s = A[1]; A[1] = B[1]; B[1] = s; s = A[2]; A[2] = B[2]; B[2] = s; s = A[3]; A[3] = B[3]; B[3] = s; a += 16; b += 16; } while (a < t); } if (low != 0) { s = A[0]; A[0] = B[0]; B[0] = s; if (low >= 8) { s = A[1]; A[1] = B[1]; B[1] = s; if (low == 12) {s = A[2]; A[2] = B[2]; B[2] = s;}}} } else { register char *t = a + size; do {s = *a; *a++ = *b; *b++ = s;} while (a < t); } } #define mmswap(a,b) mmswap_((a),(b),mmarg) static void mmrot3_(a, b, c, mmarg) register char *a, *b, *c; int mmarg; { register int s; if (mmkind >= 0) { if (mmkind > 0) { register char *t = a + high; do { s = A[0]; A[0] = B[0]; B[0] = C[0]; C[0] = s; s = A[1]; A[1] = B[1]; B[1] = C[1]; C[1] = s; s = A[2]; A[2] = B[2]; B[2] = C[2]; C[2] = s; s = A[3]; A[3] = B[3]; B[3] = C[3]; C[3] = s; a += 16; b += 16; c += 16; } while (a < t); } if (low != 0) { s = A[0]; A[0] = B[0]; B[0] = C[0]; C[0] = s; if (low >= 8) { s = A[1]; A[1] = B[1]; B[1] = C[1]; C[1] = s; if (low == 12) {s = A[2]; A[2] = B[2]; B[2] = C[2]; C[2] = s;}}} } else { register char *t = a + size; do {s = *a; *a++ = *b; *b++ = *c; *c++ = s;} while (a < t); } } #define mmrot3(a,b,c) mmrot3_((a),(b),(c),mmarg) /* qs6.c */ /*****************************************************/ /* */ /* qs6 (Quick sort function) */ /* */ /* by Tomoyuki Kawamura 1995.4.21 */ /* kawamura@tokuyama.ac.jp */ /*****************************************************/ typedef struct { char *LL, *RR; } stack_node; /* Stack structure for L,l,R,r */ #define PUSH(ll,rr) do { top->LL = (ll); top->RR = (rr); ++top; } while (0) /* Push L,l,R,r */ #define POP(ll,rr) do { --top; ll = top->LL; rr = top->RR; } while (0) /* Pop L,l,R,r */ #define med3(a,b,c) ((*cmp)(a,b,d)<0 ? \ ((*cmp)(b,c,d)<0 ? b : ((*cmp)(a,c,d)<0 ? c : a)) : \ ((*cmp)(b,c,d)>0 ? b : ((*cmp)(a,c,d)<0 ? a : c))) void ruby_qsort (base, nel, size, cmp, d) void* base; const int nel; const int size; int (*cmp)(); void *d; { register char *l, *r, *m; /* l,r:left,right group m:median point */ register int t, eq_l, eq_r; /* eq_l: all items in left group are equal to S */ char *L = base; /* left end of curren region */ char *R = (char*)base + size*(nel-1); /* right end of current region */ int chklim = 63; /* threshold of ordering element check */ stack_node stack[32], *top = stack; /* 32 is enough for 32bit CPU */ int mmkind, high, low; if (nel <= 1) return; /* need not to sort */ mmprepare(base, size); goto start; nxt: if (stack == top) return; /* return if stack is empty */ POP(L,R); for (;;) { start: if (L + size == R) { /* 2 elements */ if ((*cmp)(L,R,d) > 0) mmswap(L,R); goto nxt; } l = L; r = R; t = (r - l + size) / size; /* number of elements */ m = l + size * (t >> 1); /* calculate median value */ if (t >= 60) { register char *m1; register char *m3; if (t >= 200) { t = size*(t>>3); /* number of bytes in splitting 8 */ { register char *p1 = l + t; register char *p2 = p1 + t; register char *p3 = p2 + t; m1 = med3(p1, p2, p3); p1 = m + t; p2 = p1 + t; p3 = p2 + t; m3 = med3(p1, p2, p3); } } else { t = size*(t>>2); /* number of bytes in splitting 4 */ m1 = l + t; m3 = m + t; } m = med3(m1, m, m3); } if ((t = (*cmp)(l,m,d)) < 0) { /*3-5-?*/ if ((t = (*cmp)(m,r,d)) < 0) { /*3-5-7*/ if (chklim && nel >= chklim) { /* check if already ascending order */ char *p; chklim = 0; for (p=l; p 0) goto fail; goto nxt; } fail: goto loopA; /*3-5-7*/ } if (t > 0) { if ((*cmp)(l,r,d) <= 0) {mmswap(m,r); goto loopA;} /*3-5-4*/ mmrot3(r,m,l); goto loopA; /*3-5-2*/ } goto loopB; /*3-5-5*/ } if (t > 0) { /*7-5-?*/ if ((t = (*cmp)(m,r,d)) > 0) { /*7-5-3*/ if (chklim && nel >= chklim) { /* check if already ascending order */ char *p; chklim = 0; for (p=l; p 0) {mmswap(l,r); goto loopB;} /*5-5-3*/ /* determining splitting type in case 5-5-5 */ /*5-5-5*/ for (;;) { if ((l += size) == r) goto nxt; /*5-5-5*/ if (l == m) continue; if ((t = (*cmp)(l,m,d)) > 0) {mmswap(l,r); l = L; goto loopA;}/*575-5*/ if (t < 0) {mmswap(L,l); l = L; goto loopB;} /*535-5*/ } loopA: eq_l = 1; eq_r = 1; /* splitting type A */ /* left <= median < right */ for (;;) { for (;;) { if ((l += size) == r) {l -= size; if (l != m) mmswap(m,l); l -= size; goto fin;} if (l == m) continue; if ((t = (*cmp)(l,m,d)) > 0) {eq_r = 0; break;} if (t < 0) eq_l = 0; } for (;;) { if (l == (r -= size)) {l -= size; if (l != m) mmswap(m,l); l -= size; goto fin;} if (r == m) {m = l; break;} if ((t = (*cmp)(r,m,d)) < 0) {eq_l = 0; break;} if (t == 0) break; } mmswap(l,r); /* swap left and right */ } loopB: eq_l = 1; eq_r = 1; /* splitting type B */ /* left < median <= right */ for (;;) { for (;;) { if (l == (r -= size)) {r += size; if (r != m) mmswap(r,m); r += size; goto fin;} if (r == m) continue; if ((t = (*cmp)(r,m,d)) < 0) {eq_l = 0; break;} if (t > 0) eq_r = 0; } for (;;) { if ((l += size) == r) {r += size; if (r != m) mmswap(r,m); r += size; goto fin;} if (l == m) {m = r; break;} if ((t = (*cmp)(l,m,d)) > 0) {eq_r = 0; break;} if (t == 0) break; } mmswap(l,r); /* swap left and right */ } fin: if (eq_l == 0) /* need to sort left side */ if (eq_r == 0) /* need to sort right side */ if (l-L < R-r) {PUSH(r,R); R = l;} /* sort left side first */ else {PUSH(L,l); L = r;} /* sort right side first */ else R = l; /* need to sort left side only */ else if (eq_r == 0) L = r; /* need to sort right side only */ else goto nxt; /* need not to sort both sides */ } } char * ruby_strdup(str) const char *str; { char *tmp; int len = strlen(str) + 1; tmp = xmalloc(len); memcpy(tmp, str, len); return tmp; } char * ruby_getcwd() { #ifdef HAVE_GETCWD int size = 200; char *buf = xmalloc(size); while (!getcwd(buf, size)) { if (errno != ERANGE) { free(buf); rb_sys_fail("getcwd"); } size *= 2; buf = xrealloc(buf, size); } #else # ifndef PATH_MAX # define PATH_MAX 8192 # endif char *buf = xmalloc(PATH_MAX+1); if (!getwd(buf)) { free(buf); rb_sys_fail("getwd"); } #endif return buf; } /**************************************************************** * * The author of this software is David M. Gay. * * Copyright (c) 1991, 2000, 2001 by Lucent Technologies. * * Permission to use, copy, modify, and distribute this software for any * purpose without fee is hereby granted, provided that this entire notice * is included in all copies of any software which is or includes a copy * or modification of this software and in all copies of the supporting * documentation for such software. * * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED * WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR LUCENT MAKES ANY * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. * ***************************************************************/ /* Please send bug reports to David M. Gay (dmg at acm dot org, * with " at " changed at "@" and " dot " changed to "."). */ /* On a machine with IEEE extended-precision registers, it is * necessary to specify double-precision (53-bit) rounding precision * before invoking strtod or dtoa. If the machine uses (the equivalent * of) Intel 80x87 arithmetic, the call * _control87(PC_53, MCW_PC); * does this with many compilers. Whether this or another call is * appropriate depends on the compiler; for this to work, it may be * necessary to #include "float.h" or another system-dependent header * file. */ /* strtod for IEEE-, VAX-, and IBM-arithmetic machines. * * This strtod returns a nearest machine number to the input decimal * string (or sets errno to ERANGE). With IEEE arithmetic, ties are * broken by the IEEE round-even rule. Otherwise ties are broken by * biased rounding (add half and chop). * * Inspired loosely by William D. Clinger's paper "How to Read Floating * Point Numbers Accurately" [Proc. ACM SIGPLAN '90, pp. 92-101]. * * Modifications: * * 1. We only require IEEE, IBM, or VAX double-precision * arithmetic (not IEEE double-extended). * 2. We get by with floating-point arithmetic in a case that * Clinger missed -- when we're computing d * 10^n * for a small integer d and the integer n is not too * much larger than 22 (the maximum integer k for which * we can represent 10^k exactly), we may be able to * compute (d*10^k) * 10^(e-k) with just one roundoff. * 3. Rather than a bit-at-a-time adjustment of the binary * result in the hard case, we use floating-point * arithmetic to determine the adjustment to within * one bit; only in really hard cases do we need to * compute a second residual. * 4. Because of 3., we don't need a large table of powers of 10 * for ten-to-e (just some small tables, e.g. of 10^k * for 0 <= k <= 22). */ /* * #define IEEE_LITTLE_ENDIAN for IEEE-arithmetic machines where the least * significant byte has the lowest address. * #define IEEE_BIG_ENDIAN for IEEE-arithmetic machines where the most * significant byte has the lowest address. * #define Long int on machines with 32-bit ints and 64-bit longs. * #define IBM for IBM mainframe-style floating-point arithmetic. * #define VAX for VAX-style floating-point arithmetic (D_floating). * #define No_leftright to omit left-right logic in fast floating-point * computation of dtoa. * #define Honor_FLT_ROUNDS if FLT_ROUNDS can assume the values 2 or 3 * and strtod and dtoa should round accordingly. * #define Check_FLT_ROUNDS if FLT_ROUNDS can assume the values 2 or 3 * and Honor_FLT_ROUNDS is not #defined. * #define RND_PRODQUOT to use rnd_prod and rnd_quot (assembly routines * that use extended-precision instructions to compute rounded * products and quotients) with IBM. * #define ROUND_BIASED for IEEE-format with biased rounding. * #define Inaccurate_Divide for IEEE-format with correctly rounded * products but inaccurate quotients, e.g., for Intel i860. * #define NO_LONG_LONG on machines that do not have a "long long" * integer type (of >= 64 bits). On such machines, you can * #define Just_16 to store 16 bits per 32-bit Long when doing * high-precision integer arithmetic. Whether this speeds things * up or slows things down depends on the machine and the number * being converted. If long long is available and the name is * something other than "long long", #define Llong to be the name, * and if "unsigned Llong" does not work as an unsigned version of * Llong, #define #ULLong to be the corresponding unsigned type. * #define KR_headers for old-style C function headers. * #define Bad_float_h if your system lacks a float.h or if it does not * define some or all of DBL_DIG, DBL_MAX_10_EXP, DBL_MAX_EXP, * FLT_RADIX, FLT_ROUNDS, and DBL_MAX. * #define MALLOC your_malloc, where your_malloc(n) acts like malloc(n) * if memory is available and otherwise does something you deem * appropriate. If MALLOC is undefined, malloc will be invoked * directly -- and assumed always to succeed. * #define Omit_Private_Memory to omit logic (added Jan. 1998) for making * memory allocations from a private pool of memory when possible. * When used, the private pool is PRIVATE_MEM bytes long: 2304 bytes, * unless #defined to be a different length. This default length * suffices to get rid of MALLOC calls except for unusual cases, * such as decimal-to-binary conversion of a very long string of * digits. The longest string dtoa can return is about 751 bytes * long. For conversions by strtod of strings of 800 digits and * all dtoa conversions in single-threaded executions with 8-byte * pointers, PRIVATE_MEM >= 7400 appears to suffice; with 4-byte * pointers, PRIVATE_MEM >= 7112 appears adequate. * #define INFNAN_CHECK on IEEE systems to cause strtod to check for * Infinity and NaN (case insensitively). On some systems (e.g., * some HP systems), it may be necessary to #define NAN_WORD0 * appropriately -- to the most significant word of a quiet NaN. * (On HP Series 700/800 machines, -DNAN_WORD0=0x7ff40000 works.) * When INFNAN_CHECK is #defined and No_Hex_NaN is not #defined, * strtod also accepts (case insensitively) strings of the form * NaN(x), where x is a string of hexadecimal digits and spaces; * if there is only one string of hexadecimal digits, it is taken * for the 52 fraction bits of the resulting NaN; if there are two * or more strings of hex digits, the first is for the high 20 bits, * the second and subsequent for the low 32 bits, with intervening * white space ignored; but if this results in none of the 52 * fraction bits being on (an IEEE Infinity symbol), then NAN_WORD0 * and NAN_WORD1 are used instead. * #define MULTIPLE_THREADS if the system offers preemptively scheduled * multiple threads. In this case, you must provide (or suitably * #define) two locks, acquired by ACQUIRE_DTOA_LOCK(n) and freed * by FREE_DTOA_LOCK(n) for n = 0 or 1. (The second lock, accessed * in pow5mult, ensures lazy evaluation of only one copy of high * powers of 5; omitting this lock would introduce a small * probability of wasting memory, but would otherwise be harmless.) * You must also invoke freedtoa(s) to free the value s returned by * dtoa. You may do so whether or not MULTIPLE_THREADS is #defined. * #define NO_IEEE_Scale to disable new (Feb. 1997) logic in strtod that * avoids underflows on inputs whose result does not underflow. * If you #define NO_IEEE_Scale on a machine that uses IEEE-format * floating-point numbers and flushes underflows to zero rather * than implementing gradual underflow, then you must also #define * Sudden_Underflow. * #define YES_ALIAS to permit aliasing certain double values with * arrays of ULongs. This leads to slightly better code with * some compilers and was always used prior to 19990916, but it * is not strictly legal and can cause trouble with aggressively * optimizing compilers (e.g., gcc 2.95.1 under -O2). * #define USE_LOCALE to use the current locale's decimal_point value. * #define SET_INEXACT if IEEE arithmetic is being used and extra * computation should be done to set the inexact flag when the * result is inexact and avoid setting inexact when the result * is exact. In this case, dtoa.c must be compiled in * an environment, perhaps provided by #include "dtoa.c" in a * suitable wrapper, that defines two functions, * int get_inexact(void); * void clear_inexact(void); * such that get_inexact() returns a nonzero value if the * inexact bit is already set, and clear_inexact() sets the * inexact bit to 0. When SET_INEXACT is #defined, strtod * also does extra computations to set the underflow and overflow * flags when appropriate (i.e., when the result is tiny and * inexact or when it is a numeric value rounded to +-infinity). * #define NO_ERRNO if strtod should not assign errno = ERANGE when * the result overflows to +-Infinity or underflows to 0. */ #ifdef WORDS_BIGENDIAN #define IEEE_BIG_ENDIAN #else #define IEEE_LITTLE_ENDIAN #endif #ifdef __vax__ #define VAX #undef IEEE_BIG_ENDIAN #undef IEEE_LITTLE_ENDIAN #endif #if defined(__arm__) && !defined(__VFP_FP__) #define IEEE_BIG_ENDIAN #undef IEEE_LITTLE_ENDIAN #endif #undef Long #undef ULong #if SIZEOF_INT == 4 #define Long int #define ULong unsigned int #elif SIZEOF_LONG == 4 #define Long long int #define ULong unsigned long int #endif #if HAVE_LONG_LONG #define Llong LONG_LONG #endif #ifdef DEBUG #include "stdio.h" #define Bug(x) {fprintf(stderr, "%s\n", x); exit(1);} #endif #include "stdlib.h" #include "string.h" #ifdef USE_LOCALE #include "locale.h" #endif #ifdef MALLOC extern void *MALLOC(size_t); #else #define MALLOC malloc #endif #ifndef Omit_Private_Memory #ifndef PRIVATE_MEM #define PRIVATE_MEM 2304 #endif #define PRIVATE_mem ((PRIVATE_MEM+sizeof(double)-1)/sizeof(double)) static double private_mem[PRIVATE_mem], *pmem_next = private_mem; #endif #undef IEEE_Arith #undef Avoid_Underflow #ifdef IEEE_BIG_ENDIAN #define IEEE_Arith #endif #ifdef IEEE_LITTLE_ENDIAN #define IEEE_Arith #endif #include "errno.h" #ifdef Bad_float_h #ifdef IEEE_Arith #define DBL_DIG 15 #define DBL_MAX_10_EXP 308 #define DBL_MAX_EXP 1024 #define FLT_RADIX 2 #endif /*IEEE_Arith*/ #ifdef IBM #define DBL_DIG 16 #define DBL_MAX_10_EXP 75 #define DBL_MAX_EXP 63 #define FLT_RADIX 16 #define DBL_MAX 7.2370055773322621e+75 #endif #ifdef VAX #define DBL_DIG 16 #define DBL_MAX_10_EXP 38 #define DBL_MAX_EXP 127 #define FLT_RADIX 2 #define DBL_MAX 1.7014118346046923e+38 #endif #ifndef LONG_MAX #define LONG_MAX 2147483647 #endif #else /* ifndef Bad_float_h */ #include "float.h" #endif /* Bad_float_h */ #ifndef __MATH_H__ #include "math.h" #endif #ifdef __cplusplus extern "C" { #endif #if defined(IEEE_LITTLE_ENDIAN) + defined(IEEE_BIG_ENDIAN) + defined(VAX) + defined(IBM) != 1 Exactly one of IEEE_LITTLE_ENDIAN, IEEE_BIG_ENDIAN, VAX, or IBM should be defined. #endif typedef union { double d; ULong L[2]; } U; #ifdef YES_ALIAS #define dval(x) x #ifdef IEEE_LITTLE_ENDIAN #define word0(x) ((ULong *)&x)[1] #define word1(x) ((ULong *)&x)[0] #else #define word0(x) ((ULong *)&x)[0] #define word1(x) ((ULong *)&x)[1] #endif #else #ifdef IEEE_LITTLE_ENDIAN #define word0(x) ((U*)&x)->L[1] #define word1(x) ((U*)&x)->L[0] #else #define word0(x) ((U*)&x)->L[0] #define word1(x) ((U*)&x)->L[1] #endif #define dval(x) ((U*)&x)->d #endif /* The following definition of Storeinc is appropriate for MIPS processors. * An alternative that might be better on some machines is * #define Storeinc(a,b,c) (*a++ = b << 16 | c & 0xffff) */ #if defined(IEEE_LITTLE_ENDIAN) + defined(VAX) + defined(__arm__) #define Storeinc(a,b,c) (((unsigned short *)a)[1] = (unsigned short)b, \ ((unsigned short *)a)[0] = (unsigned short)c, a++) #else #define Storeinc(a,b,c) (((unsigned short *)a)[0] = (unsigned short)b, \ ((unsigned short *)a)[1] = (unsigned short)c, a++) #endif /* #define P DBL_MANT_DIG */ /* Ten_pmax = floor(P*log(2)/log(5)) */ /* Bletch = (highest power of 2 < DBL_MAX_10_EXP) / 16 */ /* Quick_max = floor((P-1)*log(FLT_RADIX)/log(10) - 1) */ /* Int_max = floor(P*log(FLT_RADIX)/log(10) - 1) */ #ifdef IEEE_Arith #define Exp_shift 20 #define Exp_shift1 20 #define Exp_msk1 0x100000 #define Exp_msk11 0x100000 #define Exp_mask 0x7ff00000 #define P 53 #define Bias 1023 #define Emin (-1022) #define Exp_1 0x3ff00000 #define Exp_11 0x3ff00000 #define Ebits 11 #define Frac_mask 0xfffff #define Frac_mask1 0xfffff #define Ten_pmax 22 #define Bletch 0x10 #define Bndry_mask 0xfffff #define Bndry_mask1 0xfffff #define LSB 1 #define Sign_bit 0x80000000 #define Log2P 1 #define Tiny0 0 #define Tiny1 1 #define Quick_max 14 #define Int_max 14 #ifndef NO_IEEE_Scale #define Avoid_Underflow #ifdef Flush_Denorm /* debugging option */ #undef Sudden_Underflow #endif #endif #ifndef Flt_Rounds #ifdef FLT_ROUNDS #define Flt_Rounds FLT_ROUNDS #else #define Flt_Rounds 1 #endif #endif /*Flt_Rounds*/ #ifdef Honor_FLT_ROUNDS #define Rounding rounding #undef Check_FLT_ROUNDS #define Check_FLT_ROUNDS #else #define Rounding Flt_Rounds #endif #else /* ifndef IEEE_Arith */ #undef Check_FLT_ROUNDS #undef Honor_FLT_ROUNDS #undef SET_INEXACT #undef Sudden_Underflow #define Sudden_Underflow #ifdef IBM #undef Flt_Rounds #define Flt_Rounds 0 #define Exp_shift 24 #define Exp_shift1 24 #define Exp_msk1 0x1000000 #define Exp_msk11 0x1000000 #define Exp_mask 0x7f000000 #define P 14 #define Bias 65 #define Exp_1 0x41000000 #define Exp_11 0x41000000 #define Ebits 8 /* exponent has 7 bits, but 8 is the right value in b2d */ #define Frac_mask 0xffffff #define Frac_mask1 0xffffff #define Bletch 4 #define Ten_pmax 22 #define Bndry_mask 0xefffff #define Bndry_mask1 0xffffff #define LSB 1 #define Sign_bit 0x80000000 #define Log2P 4 #define Tiny0 0x100000 #define Tiny1 0 #define Quick_max 14 #define Int_max 15 #else /* VAX */ #undef Flt_Rounds #define Flt_Rounds 1 #define Exp_shift 23 #define Exp_shift1 7 #define Exp_msk1 0x80 #define Exp_msk11 0x800000 #define Exp_mask 0x7f80 #define P 56 #define Bias 129 #define Exp_1 0x40800000 #define Exp_11 0x4080 #define Ebits 8 #define Frac_mask 0x7fffff #define Frac_mask1 0xffff007f #define Ten_pmax 24 #define Bletch 2 #define Bndry_mask 0xffff007f #define Bndry_mask1 0xffff007f #define LSB 0x10000 #define Sign_bit 0x8000 #define Log2P 1 #define Tiny0 0x80 #define Tiny1 0 #define Quick_max 15 #define Int_max 15 #endif /* IBM, VAX */ #endif /* IEEE_Arith */ #ifndef IEEE_Arith #define ROUND_BIASED #endif #ifdef RND_PRODQUOT #define rounded_product(a,b) a = rnd_prod(a, b) #define rounded_quotient(a,b) a = rnd_quot(a, b) extern double rnd_prod(double, double), rnd_quot(double, double); #else #define rounded_product(a,b) a *= b #define rounded_quotient(a,b) a /= b #endif #define Big0 (Frac_mask1 | Exp_msk1*(DBL_MAX_EXP+Bias-1)) #define Big1 0xffffffff #ifndef Pack_32 #define Pack_32 #endif #define FFFFFFFF 0xffffffffUL #ifdef NO_LONG_LONG #undef ULLong #ifdef Just_16 #undef Pack_32 /* When Pack_32 is not defined, we store 16 bits per 32-bit Long. * This makes some inner loops simpler and sometimes saves work * during multiplications, but it often seems to make things slightly * slower. Hence the default is now to store 32 bits per Long. */ #endif #else /* long long available */ #ifndef Llong #define Llong long long #endif #ifndef ULLong #define ULLong unsigned Llong #endif #endif /* NO_LONG_LONG */ #ifndef MULTIPLE_THREADS #define ACQUIRE_DTOA_LOCK(n) /*nothing*/ #define FREE_DTOA_LOCK(n) /*nothing*/ #endif #define Kmax 15 struct Bigint { struct Bigint *next; int k, maxwds, sign, wds; ULong x[1]; }; typedef struct Bigint Bigint; static Bigint *freelist[Kmax+1]; static Bigint * Balloc(int k) { int x; Bigint *rv; #ifndef Omit_Private_Memory unsigned int len; #endif ACQUIRE_DTOA_LOCK(0); if ((rv = freelist[k]) != 0) { freelist[k] = rv->next; } else { x = 1 << k; #ifdef Omit_Private_Memory rv = (Bigint *)MALLOC(sizeof(Bigint) + (x-1)*sizeof(ULong)); #else len = (sizeof(Bigint) + (x-1)*sizeof(ULong) + sizeof(double) - 1) /sizeof(double); if (pmem_next - private_mem + len <= PRIVATE_mem) { rv = (Bigint*)pmem_next; pmem_next += len; } else rv = (Bigint*)MALLOC(len*sizeof(double)); #endif rv->k = k; rv->maxwds = x; } FREE_DTOA_LOCK(0); rv->sign = rv->wds = 0; return rv; } static void Bfree(Bigint *v) { if (v) { ACQUIRE_DTOA_LOCK(0); v->next = freelist[v->k]; freelist[v->k] = v; FREE_DTOA_LOCK(0); } } #define Bcopy(x,y) memcpy((char *)&x->sign, (char *)&y->sign, \ y->wds*sizeof(Long) + 2*sizeof(int)) static Bigint * multadd(Bigint *b, int m, int a) /* multiply by m and add a */ { int i, wds; #ifdef ULLong ULong *x; ULLong carry, y; #else ULong carry, *x, y; #ifdef Pack_32 ULong xi, z; #endif #endif Bigint *b1; wds = b->wds; x = b->x; i = 0; carry = a; do { #ifdef ULLong y = *x * (ULLong)m + carry; carry = y >> 32; *x++ = y & FFFFFFFF; #else #ifdef Pack_32 xi = *x; y = (xi & 0xffff) * m + carry; z = (xi >> 16) * m + (y >> 16); carry = z >> 16; *x++ = (z << 16) + (y & 0xffff); #else y = *x * m + carry; carry = y >> 16; *x++ = y & 0xffff; #endif #endif } while (++i < wds); if (carry) { if (wds >= b->maxwds) { b1 = Balloc(b->k+1); Bcopy(b1, b); Bfree(b); b = b1; } b->x[wds++] = carry; b->wds = wds; } return b; } static Bigint * s2b(const char *s, int nd0, int nd, ULong y9) { Bigint *b; int i, k; Long x, y; x = (nd + 8) / 9; for (k = 0, y = 1; x > y; y <<= 1, k++) ; #ifdef Pack_32 b = Balloc(k); b->x[0] = y9; b->wds = 1; #else b = Balloc(k+1); b->x[0] = y9 & 0xffff; b->wds = (b->x[1] = y9 >> 16) ? 2 : 1; #endif i = 9; if (9 < nd0) { s += 9; do { b = multadd(b, 10, *s++ - '0'); } while (++i < nd0); s++; } else s += 10; for (; i < nd; i++) b = multadd(b, 10, *s++ - '0'); return b; } static int hi0bits(register ULong x) { register int k = 0; if (!(x & 0xffff0000)) { k = 16; x <<= 16; } if (!(x & 0xff000000)) { k += 8; x <<= 8; } if (!(x & 0xf0000000)) { k += 4; x <<= 4; } if (!(x & 0xc0000000)) { k += 2; x <<= 2; } if (!(x & 0x80000000)) { k++; if (!(x & 0x40000000)) return 32; } return k; } static int lo0bits(ULong *y) { register int k; register ULong x = *y; if (x & 7) { if (x & 1) return 0; if (x & 2) { *y = x >> 1; return 1; } *y = x >> 2; return 2; } k = 0; if (!(x & 0xffff)) { k = 16; x >>= 16; } if (!(x & 0xff)) { k += 8; x >>= 8; } if (!(x & 0xf)) { k += 4; x >>= 4; } if (!(x & 0x3)) { k += 2; x >>= 2; } if (!(x & 1)) { k++; x >>= 1; if (!x) return 32; } *y = x; return k; } static Bigint * i2b(int i) { Bigint *b; b = Balloc(1); b->x[0] = i; b->wds = 1; return b; } static Bigint * mult(Bigint *a, Bigint *b) { Bigint *c; int k, wa, wb, wc; ULong *x, *xa, *xae, *xb, *xbe, *xc, *xc0; ULong y; #ifdef ULLong ULLong carry, z; #else ULong carry, z; #ifdef Pack_32 ULong z2; #endif #endif if (a->wds < b->wds) { c = a; a = b; b = c; } k = a->k; wa = a->wds; wb = b->wds; wc = wa + wb; if (wc > a->maxwds) k++; c = Balloc(k); for (x = c->x, xa = x + wc; x < xa; x++) *x = 0; xa = a->x; xae = xa + wa; xb = b->x; xbe = xb + wb; xc0 = c->x; #ifdef ULLong for (; xb < xbe; xc0++) { if ((y = *xb++) != 0) { x = xa; xc = xc0; carry = 0; do { z = *x++ * (ULLong)y + *xc + carry; carry = z >> 32; *xc++ = z & FFFFFFFF; } while (x < xae); *xc = carry; } } #else #ifdef Pack_32 for (; xb < xbe; xb++, xc0++) { if (y = *xb & 0xffff) { x = xa; xc = xc0; carry = 0; do { z = (*x & 0xffff) * y + (*xc & 0xffff) + carry; carry = z >> 16; z2 = (*x++ >> 16) * y + (*xc >> 16) + carry; carry = z2 >> 16; Storeinc(xc, z2, z); } while (x < xae); *xc = carry; } if (y = *xb >> 16) { x = xa; xc = xc0; carry = 0; z2 = *xc; do { z = (*x & 0xffff) * y + (*xc >> 16) + carry; carry = z >> 16; Storeinc(xc, z, z2); z2 = (*x++ >> 16) * y + (*xc & 0xffff) + carry; carry = z2 >> 16; } while (x < xae); *xc = z2; } } #else for (; xb < xbe; xc0++) { if (y = *xb++) { x = xa; xc = xc0; carry = 0; do { z = *x++ * y + *xc + carry; carry = z >> 16; *xc++ = z & 0xffff; } while (x < xae); *xc = carry; } } #endif #endif for (xc0 = c->x, xc = xc0 + wc; wc > 0 && !*--xc; --wc) ; c->wds = wc; return c; } static Bigint *p5s; static Bigint * pow5mult(Bigint *b, int k) { Bigint *b1, *p5, *p51; int i; static int p05[3] = { 5, 25, 125 }; if ((i = k & 3) != 0) b = multadd(b, p05[i-1], 0); if (!(k >>= 2)) return b; if (!(p5 = p5s)) { /* first time */ #ifdef MULTIPLE_THREADS ACQUIRE_DTOA_LOCK(1); if (!(p5 = p5s)) { p5 = p5s = i2b(625); p5->next = 0; } FREE_DTOA_LOCK(1); #else p5 = p5s = i2b(625); p5->next = 0; #endif } for (;;) { if (k & 1) { b1 = mult(b, p5); Bfree(b); b = b1; } if (!(k >>= 1)) break; if (!(p51 = p5->next)) { #ifdef MULTIPLE_THREADS ACQUIRE_DTOA_LOCK(1); if (!(p51 = p5->next)) { p51 = p5->next = mult(p5,p5); p51->next = 0; } FREE_DTOA_LOCK(1); #else p51 = p5->next = mult(p5,p5); p51->next = 0; #endif } p5 = p51; } return b; } static Bigint * lshift(Bigint *b, int k) { int i, k1, n, n1; Bigint *b1; ULong *x, *x1, *xe, z; #ifdef Pack_32 n = k >> 5; #else n = k >> 4; #endif k1 = b->k; n1 = n + b->wds + 1; for (i = b->maxwds; n1 > i; i <<= 1) k1++; b1 = Balloc(k1); x1 = b1->x; for (i = 0; i < n; i++) *x1++ = 0; x = b->x; xe = x + b->wds; #ifdef Pack_32 if (k &= 0x1f) { k1 = 32 - k; z = 0; do { *x1++ = *x << k | z; z = *x++ >> k1; } while (x < xe); if ((*x1 = z) != 0) ++n1; } #else if (k &= 0xf) { k1 = 16 - k; z = 0; do { *x1++ = *x << k & 0xffff | z; z = *x++ >> k1; } while (x < xe); if (*x1 = z) ++n1; } #endif else do { *x1++ = *x++; } while (x < xe); b1->wds = n1 - 1; Bfree(b); return b1; } static int cmp(Bigint *a, Bigint *b) { ULong *xa, *xa0, *xb, *xb0; int i, j; i = a->wds; j = b->wds; #ifdef DEBUG if (i > 1 && !a->x[i-1]) Bug("cmp called with a->x[a->wds-1] == 0"); if (j > 1 && !b->x[j-1]) Bug("cmp called with b->x[b->wds-1] == 0"); #endif if (i -= j) return i; xa0 = a->x; xa = xa0 + j; xb0 = b->x; xb = xb0 + j; for (;;) { if (*--xa != *--xb) return *xa < *xb ? -1 : 1; if (xa <= xa0) break; } return 0; } static Bigint * diff(Bigint *a, Bigint *b) { Bigint *c; int i, wa, wb; ULong *xa, *xae, *xb, *xbe, *xc; #ifdef ULLong ULLong borrow, y; #else ULong borrow, y; #ifdef Pack_32 ULong z; #endif #endif i = cmp(a,b); if (!i) { c = Balloc(0); c->wds = 1; c->x[0] = 0; return c; } if (i < 0) { c = a; a = b; b = c; i = 1; } else i = 0; c = Balloc(a->k); c->sign = i; wa = a->wds; xa = a->x; xae = xa + wa; wb = b->wds; xb = b->x; xbe = xb + wb; xc = c->x; borrow = 0; #ifdef ULLong do { y = (ULLong)*xa++ - *xb++ - borrow; borrow = y >> 32 & (ULong)1; *xc++ = y & FFFFFFFF; } while (xb < xbe); while (xa < xae) { y = *xa++ - borrow; borrow = y >> 32 & (ULong)1; *xc++ = y & FFFFFFFF; } #else #ifdef Pack_32 do { y = (*xa & 0xffff) - (*xb & 0xffff) - borrow; borrow = (y & 0x10000) >> 16; z = (*xa++ >> 16) - (*xb++ >> 16) - borrow; borrow = (z & 0x10000) >> 16; Storeinc(xc, z, y); } while (xb < xbe); while (xa < xae) { y = (*xa & 0xffff) - borrow; borrow = (y & 0x10000) >> 16; z = (*xa++ >> 16) - borrow; borrow = (z & 0x10000) >> 16; Storeinc(xc, z, y); } #else do { y = *xa++ - *xb++ - borrow; borrow = (y & 0x10000) >> 16; *xc++ = y & 0xffff; } while (xb < xbe); while (xa < xae) { y = *xa++ - borrow; borrow = (y & 0x10000) >> 16; *xc++ = y & 0xffff; } #endif #endif while (!*--xc) wa--; c->wds = wa; return c; } static double ulp(double x) { register Long L; double a; L = (word0(x) & Exp_mask) - (P-1)*Exp_msk1; #ifndef Avoid_Underflow #ifndef Sudden_Underflow if (L > 0) { #endif #endif #ifdef IBM L |= Exp_msk1 >> 4; #endif word0(a) = L; word1(a) = 0; #ifndef Avoid_Underflow #ifndef Sudden_Underflow } else { L = -L >> Exp_shift; if (L < Exp_shift) { word0(a) = 0x80000 >> L; word1(a) = 0; } else { word0(a) = 0; L -= Exp_shift; word1(a) = L >= 31 ? 1 : 1 << 31 - L; } } #endif #endif return dval(a); } static double b2d(Bigint *a, int *e) { ULong *xa, *xa0, w, y, z; int k; double d; #ifdef VAX ULong d0, d1; #else #define d0 word0(d) #define d1 word1(d) #endif xa0 = a->x; xa = xa0 + a->wds; y = *--xa; #ifdef DEBUG if (!y) Bug("zero y in b2d"); #endif k = hi0bits(y); *e = 32 - k; #ifdef Pack_32 if (k < Ebits) { d0 = Exp_1 | y >> (Ebits - k); w = xa > xa0 ? *--xa : 0; d1 = y << ((32-Ebits) + k) | w >> (Ebits - k); goto ret_d; } z = xa > xa0 ? *--xa : 0; if (k -= Ebits) { d0 = Exp_1 | y << k | z >> (32 - k); y = xa > xa0 ? *--xa : 0; d1 = z << k | y >> (32 - k); } else { d0 = Exp_1 | y; d1 = z; } #else if (k < Ebits + 16) { z = xa > xa0 ? *--xa : 0; d0 = Exp_1 | y << k - Ebits | z >> Ebits + 16 - k; w = xa > xa0 ? *--xa : 0; y = xa > xa0 ? *--xa : 0; d1 = z << k + 16 - Ebits | w << k - Ebits | y >> 16 + Ebits - k; goto ret_d; } z = xa > xa0 ? *--xa : 0; w = xa > xa0 ? *--xa : 0; k -= Ebits + 16; d0 = Exp_1 | y << k + 16 | z << k | w >> 16 - k; y = xa > xa0 ? *--xa : 0; d1 = w << k + 16 | y << k; #endif ret_d: #ifdef VAX word0(d) = d0 >> 16 | d0 << 16; word1(d) = d1 >> 16 | d1 << 16; #else #undef d0 #undef d1 #endif return dval(d); } static Bigint * d2b(double d, int *e, int *bits) { Bigint *b; int de, k; ULong *x, y, z; #ifndef Sudden_Underflow int i; #endif #ifdef VAX ULong d0, d1; d0 = word0(d) >> 16 | word0(d) << 16; d1 = word1(d) >> 16 | word1(d) << 16; #else #define d0 word0(d) #define d1 word1(d) #endif #ifdef Pack_32 b = Balloc(1); #else b = Balloc(2); #endif x = b->x; z = d0 & Frac_mask; d0 &= 0x7fffffff; /* clear sign bit, which we ignore */ #ifdef Sudden_Underflow de = (int)(d0 >> Exp_shift); #ifndef IBM z |= Exp_msk11; #endif #else if ((de = (int)(d0 >> Exp_shift)) != 0) z |= Exp_msk1; #endif #ifdef Pack_32 if ((y = d1) != 0) { if ((k = lo0bits(&y)) != 0) { x[0] = y | z << (32 - k); z >>= k; } else x[0] = y; #ifndef Sudden_Underflow i = #endif b->wds = (x[1] = z) ? 2 : 1; } else { #ifdef DEBUG if (!z) Bug("Zero passed to d2b"); #endif k = lo0bits(&z); x[0] = z; #ifndef Sudden_Underflow i = #endif b->wds = 1; k += 32; } #else if (y = d1) { if (k = lo0bits(&y)) if (k >= 16) { x[0] = y | z << 32 - k & 0xffff; x[1] = z >> k - 16 & 0xffff; x[2] = z >> k; i = 2; } else { x[0] = y & 0xffff; x[1] = y >> 16 | z << 16 - k & 0xffff; x[2] = z >> k & 0xffff; x[3] = z >> k+16; i = 3; } else { x[0] = y & 0xffff; x[1] = y >> 16; x[2] = z & 0xffff; x[3] = z >> 16; i = 3; } } else { #ifdef DEBUG if (!z) Bug("Zero passed to d2b"); #endif k = lo0bits(&z); if (k >= 16) { x[0] = z; i = 0; } else { x[0] = z & 0xffff; x[1] = z >> 16; i = 1; } k += 32; } while (!x[i]) --i; b->wds = i + 1; #endif #ifndef Sudden_Underflow if (de) { #endif #ifdef IBM *e = (de - Bias - (P-1) << 2) + k; *bits = 4*P + 8 - k - hi0bits(word0(d) & Frac_mask); #else *e = de - Bias - (P-1) + k; *bits = P - k; #endif #ifndef Sudden_Underflow } else { *e = de - Bias - (P-1) + 1 + k; #ifdef Pack_32 *bits = 32*i - hi0bits(x[i-1]); #else *bits = (i+2)*16 - hi0bits(x[i]); #endif } #endif return b; } #undef d0 #undef d1 static double ratio(Bigint *a, Bigint *b) { double da, db; int k, ka, kb; dval(da) = b2d(a, &ka); dval(db) = b2d(b, &kb); #ifdef Pack_32 k = ka - kb + 32*(a->wds - b->wds); #else k = ka - kb + 16*(a->wds - b->wds); #endif #ifdef IBM if (k > 0) { word0(da) += (k >> 2)*Exp_msk1; if (k &= 3) dval(da) *= 1 << k; } else { k = -k; word0(db) += (k >> 2)*Exp_msk1; if (k &= 3) dval(db) *= 1 << k; } #else if (k > 0) word0(da) += k*Exp_msk1; else { k = -k; word0(db) += k*Exp_msk1; } #endif return dval(da) / dval(db); } static const double tens[] = { 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22 #ifdef VAX , 1e23, 1e24 #endif }; static const double #ifdef IEEE_Arith bigtens[] = { 1e16, 1e32, 1e64, 1e128, 1e256 }; static const double tinytens[] = { 1e-16, 1e-32, 1e-64, 1e-128, #ifdef Avoid_Underflow 9007199254740992.*9007199254740992.e-256 /* = 2^106 * 1e-53 */ #else 1e-256 #endif }; /* The factor of 2^53 in tinytens[4] helps us avoid setting the underflow */ /* flag unnecessarily. It leads to a song and dance at the end of strtod. */ #define Scale_Bit 0x10 #define n_bigtens 5 #else #ifdef IBM bigtens[] = { 1e16, 1e32, 1e64 }; static const double tinytens[] = { 1e-16, 1e-32, 1e-64 }; #define n_bigtens 3 #else bigtens[] = { 1e16, 1e32 }; static const double tinytens[] = { 1e-16, 1e-32 }; #define n_bigtens 2 #endif #endif #ifndef IEEE_Arith #undef INFNAN_CHECK #endif #ifdef INFNAN_CHECK #ifndef NAN_WORD0 #define NAN_WORD0 0x7ff80000 #endif #ifndef NAN_WORD1 #define NAN_WORD1 0 #endif static int match(const char **sp, char *t) { int c, d; const char *s = *sp; while (d = *t++) { if ((c = *++s) >= 'A' && c <= 'Z') c += 'a' - 'A'; if (c != d) return 0; } *sp = s + 1; return 1; } #ifndef No_Hex_NaN static void hexnan(double *rvp, const char **sp) { ULong c, x[2]; const char *s; int havedig, udx0, xshift; x[0] = x[1] = 0; havedig = xshift = 0; udx0 = 1; s = *sp; while (c = *(const unsigned char*)++s) { if (c >= '0' && c <= '9') c -= '0'; else if (c >= 'a' && c <= 'f') c += 10 - 'a'; else if (c >= 'A' && c <= 'F') c += 10 - 'A'; else if (c <= ' ') { if (udx0 && havedig) { udx0 = 0; xshift = 1; } continue; } else if (/*(*/ c == ')' && havedig) { *sp = s + 1; break; } else return; /* invalid form: don't change *sp */ havedig = 1; if (xshift) { xshift = 0; x[0] = x[1]; x[1] = 0; } if (udx0) x[0] = (x[0] << 4) | (x[1] >> 28); x[1] = (x[1] << 4) | c; } if ((x[0] &= 0xfffff) || x[1]) { word0(*rvp) = Exp_mask | x[0]; word1(*rvp) = x[1]; } } #endif /*No_Hex_NaN*/ #endif /* INFNAN_CHECK */ double ruby_strtod(const char *s00, char **se) { #ifdef Avoid_Underflow int scale; #endif int bb2, bb5, bbe, bd2, bd5, bbbits, bs2, c, dsign, e, e1, esign, i, j, k, nd, nd0, nf, nz, nz0, sign; const char *s, *s0, *s1; double aadj, aadj1, adj, rv, rv0; Long L; ULong y, z; Bigint *bb, *bb1, *bd, *bd0, *bs, *delta; #ifdef SET_INEXACT int inexact, oldinexact; #endif #ifdef Honor_FLT_ROUNDS int rounding; #endif #ifdef USE_LOCALE const char *s2; #endif sign = nz0 = nz = 0; dval(rv) = 0.; for (s = s00;;s++) switch (*s) { case '-': sign = 1; /* no break */ case '+': if (*++s) goto break2; /* no break */ case 0: goto ret0; case '\t': case '\n': case '\v': case '\f': case '\r': case ' ': continue; default: goto break2; } break2: if (*s == '0') { nz0 = 1; while (*++s == '0') ; if (!*s) goto ret; } s0 = s; y = z = 0; for (nd = nf = 0; (c = *s) >= '0' && c <= '9'; nd++, s++) if (nd < 9) y = 10*y + c - '0'; else if (nd < 16) z = 10*z + c - '0'; nd0 = nd; #ifdef USE_LOCALE s1 = localeconv()->decimal_point; if (c == *s1) { c = '.'; if (*++s1) { s2 = s; for (;;) { if (*++s2 != *s1) { c = 0; break; } if (!*++s1) { s = s2; break; } } } } #endif if (c == '.') { if (!ISDIGIT(s[1])) goto dig_done; c = *++s; if (!nd) { for (; c == '0'; c = *++s) nz++; if (c > '0' && c <= '9') { s0 = s; nf += nz; nz = 0; goto have_dig; } goto dig_done; } for (; c >= '0' && c <= '9'; c = *++s) { have_dig: nz++; if (c -= '0') { nf += nz; for (i = 1; i < nz; i++) if (nd++ < 9) y *= 10; else if (nd <= DBL_DIG + 1) z *= 10; if (nd++ < 9) y = 10*y + c; else if (nd <= DBL_DIG + 1) z = 10*z + c; nz = 0; } } } dig_done: e = 0; if (c == 'e' || c == 'E') { if (!nd && !nz && !nz0) { goto ret0; } s00 = s; esign = 0; switch (c = *++s) { case '-': esign = 1; case '+': c = *++s; } if (c >= '0' && c <= '9') { while (c == '0') c = *++s; if (c > '0' && c <= '9') { L = c - '0'; s1 = s; while ((c = *++s) >= '0' && c <= '9') L = 10*L + c - '0'; if (s - s1 > 8 || L > 19999) /* Avoid confusion from exponents * so large that e might overflow. */ e = 19999; /* safe for 16 bit ints */ else e = (int)L; if (esign) e = -e; } else e = 0; } else s = s00; } if (!nd) { if (!nz && !nz0) { #ifdef INFNAN_CHECK /* Check for Nan and Infinity */ switch (c) { case 'i': case 'I': if (match(&s,"nf")) { --s; if (!match(&s,"inity")) ++s; word0(rv) = 0x7ff00000; word1(rv) = 0; goto ret; } break; case 'n': case 'N': if (match(&s, "an")) { word0(rv) = NAN_WORD0; word1(rv) = NAN_WORD1; #ifndef No_Hex_NaN if (*s == '(') /*)*/ hexnan(&rv, &s); #endif goto ret; } } #endif /* INFNAN_CHECK */ ret0: s = s00; sign = 0; } goto ret; } e1 = e -= nf; /* Now we have nd0 digits, starting at s0, followed by a * decimal point, followed by nd-nd0 digits. The number we're * after is the integer represented by those digits times * 10**e */ if (!nd0) nd0 = nd; k = nd < DBL_DIG + 1 ? nd : DBL_DIG + 1; dval(rv) = y; if (k > 9) { #ifdef SET_INEXACT if (k > DBL_DIG) oldinexact = get_inexact(); #endif dval(rv) = tens[k - 9] * dval(rv) + z; } bd0 = 0; if (nd <= DBL_DIG #ifndef RND_PRODQUOT #ifndef Honor_FLT_ROUNDS && Flt_Rounds == 1 #endif #endif ) { if (!e) goto ret; if (e > 0) { if (e <= Ten_pmax) { #ifdef VAX goto vax_ovfl_check; #else #ifdef Honor_FLT_ROUNDS /* round correctly FLT_ROUNDS = 2 or 3 */ if (sign) { rv = -rv; sign = 0; } #endif /* rv = */ rounded_product(dval(rv), tens[e]); goto ret; #endif } i = DBL_DIG - nd; if (e <= Ten_pmax + i) { /* A fancier test would sometimes let us do * this for larger i values. */ #ifdef Honor_FLT_ROUNDS /* round correctly FLT_ROUNDS = 2 or 3 */ if (sign) { rv = -rv; sign = 0; } #endif e -= i; dval(rv) *= tens[i]; #ifdef VAX /* VAX exponent range is so narrow we must * worry about overflow here... */ vax_ovfl_check: word0(rv) -= P*Exp_msk1; /* rv = */ rounded_product(dval(rv), tens[e]); if ((word0(rv) & Exp_mask) > Exp_msk1*(DBL_MAX_EXP+Bias-1-P)) goto ovfl; word0(rv) += P*Exp_msk1; #else /* rv = */ rounded_product(dval(rv), tens[e]); #endif goto ret; } } #ifndef Inaccurate_Divide else if (e >= -Ten_pmax) { #ifdef Honor_FLT_ROUNDS /* round correctly FLT_ROUNDS = 2 or 3 */ if (sign) { rv = -rv; sign = 0; } #endif /* rv = */ rounded_quotient(dval(rv), tens[-e]); goto ret; } #endif } e1 += nd - k; #ifdef IEEE_Arith #ifdef SET_INEXACT inexact = 1; if (k <= DBL_DIG) oldinexact = get_inexact(); #endif #ifdef Avoid_Underflow scale = 0; #endif #ifdef Honor_FLT_ROUNDS if ((rounding = Flt_Rounds) >= 2) { if (sign) rounding = rounding == 2 ? 0 : 2; else if (rounding != 2) rounding = 0; } #endif #endif /*IEEE_Arith*/ /* Get starting approximation = rv * 10**e1 */ if (e1 > 0) { if ((i = e1 & 15) != 0) dval(rv) *= tens[i]; if (e1 &= ~15) { if (e1 > DBL_MAX_10_EXP) { ovfl: #ifndef NO_ERRNO errno = ERANGE; #endif /* Can't trust HUGE_VAL */ #ifdef IEEE_Arith #ifdef Honor_FLT_ROUNDS switch (rounding) { case 0: /* toward 0 */ case 3: /* toward -infinity */ word0(rv) = Big0; word1(rv) = Big1; break; default: word0(rv) = Exp_mask; word1(rv) = 0; } #else /*Honor_FLT_ROUNDS*/ word0(rv) = Exp_mask; word1(rv) = 0; #endif /*Honor_FLT_ROUNDS*/ #ifdef SET_INEXACT /* set overflow bit */ dval(rv0) = 1e300; dval(rv0) *= dval(rv0); #endif #else /*IEEE_Arith*/ word0(rv) = Big0; word1(rv) = Big1; #endif /*IEEE_Arith*/ if (bd0) goto retfree; goto ret; } e1 >>= 4; for (j = 0; e1 > 1; j++, e1 >>= 1) if (e1 & 1) dval(rv) *= bigtens[j]; /* The last multiplication could overflow. */ word0(rv) -= P*Exp_msk1; dval(rv) *= bigtens[j]; if ((z = word0(rv) & Exp_mask) > Exp_msk1*(DBL_MAX_EXP+Bias-P)) goto ovfl; if (z > Exp_msk1*(DBL_MAX_EXP+Bias-1-P)) { /* set to largest number */ /* (Can't trust DBL_MAX) */ word0(rv) = Big0; word1(rv) = Big1; } else word0(rv) += P*Exp_msk1; } } else if (e1 < 0) { e1 = -e1; if ((i = e1 & 15) != 0) dval(rv) /= tens[i]; if (e1 >>= 4) { if (e1 >= 1 << n_bigtens) goto undfl; #ifdef Avoid_Underflow if (e1 & Scale_Bit) scale = 2*P; for (j = 0; e1 > 0; j++, e1 >>= 1) if (e1 & 1) dval(rv) *= tinytens[j]; if (scale && (j = 2*P + 1 - ((word0(rv) & Exp_mask) >> Exp_shift)) > 0) { /* scaled rv is denormal; zap j low bits */ if (j >= 32) { word1(rv) = 0; if (j >= 53) word0(rv) = (P+2)*Exp_msk1; else word0(rv) &= 0xffffffff << (j-32); } else word1(rv) &= 0xffffffff << j; } #else for (j = 0; e1 > 1; j++, e1 >>= 1) if (e1 & 1) dval(rv) *= tinytens[j]; /* The last multiplication could underflow. */ dval(rv0) = dval(rv); dval(rv) *= tinytens[j]; if (!dval(rv)) { dval(rv) = 2.*dval(rv0); dval(rv) *= tinytens[j]; #endif if (!dval(rv)) { undfl: dval(rv) = 0.; #ifndef NO_ERRNO errno = ERANGE; #endif if (bd0) goto retfree; goto ret; } #ifndef Avoid_Underflow word0(rv) = Tiny0; word1(rv) = Tiny1; /* The refinement below will clean * this approximation up. */ } #endif } } /* Now the hard part -- adjusting rv to the correct value.*/ /* Put digits into bd: true value = bd * 10^e */ bd0 = s2b(s0, nd0, nd, y); for (;;) { bd = Balloc(bd0->k); Bcopy(bd, bd0); bb = d2b(dval(rv), &bbe, &bbbits); /* rv = bb * 2^bbe */ bs = i2b(1); if (e >= 0) { bb2 = bb5 = 0; bd2 = bd5 = e; } else { bb2 = bb5 = -e; bd2 = bd5 = 0; } if (bbe >= 0) bb2 += bbe; else bd2 -= bbe; bs2 = bb2; #ifdef Honor_FLT_ROUNDS if (rounding != 1) bs2++; #endif #ifdef Avoid_Underflow j = bbe - scale; i = j + bbbits - 1; /* logb(rv) */ if (i < Emin) /* denormal */ j += P - Emin; else j = P + 1 - bbbits; #else /*Avoid_Underflow*/ #ifdef Sudden_Underflow #ifdef IBM j = 1 + 4*P - 3 - bbbits + ((bbe + bbbits - 1) & 3); #else j = P + 1 - bbbits; #endif #else /*Sudden_Underflow*/ j = bbe; i = j + bbbits - 1; /* logb(rv) */ if (i < Emin) /* denormal */ j += P - Emin; else j = P + 1 - bbbits; #endif /*Sudden_Underflow*/ #endif /*Avoid_Underflow*/ bb2 += j; bd2 += j; #ifdef Avoid_Underflow bd2 += scale; #endif i = bb2 < bd2 ? bb2 : bd2; if (i > bs2) i = bs2; if (i > 0) { bb2 -= i; bd2 -= i; bs2 -= i; } if (bb5 > 0) { bs = pow5mult(bs, bb5); bb1 = mult(bs, bb); Bfree(bb); bb = bb1; } if (bb2 > 0) bb = lshift(bb, bb2); if (bd5 > 0) bd = pow5mult(bd, bd5); if (bd2 > 0) bd = lshift(bd, bd2); if (bs2 > 0) bs = lshift(bs, bs2); delta = diff(bb, bd); dsign = delta->sign; delta->sign = 0; i = cmp(delta, bs); #ifdef Honor_FLT_ROUNDS if (rounding != 1) { if (i < 0) { /* Error is less than an ulp */ if (!delta->x[0] && delta->wds <= 1) { /* exact */ #ifdef SET_INEXACT inexact = 0; #endif break; } if (rounding) { if (dsign) { adj = 1.; goto apply_adj; } } else if (!dsign) { adj = -1.; if (!word1(rv) && !(word0(rv) & Frac_mask)) { y = word0(rv) & Exp_mask; #ifdef Avoid_Underflow if (!scale || y > 2*P*Exp_msk1) #else if (y) #endif { delta = lshift(delta,Log2P); if (cmp(delta, bs) <= 0) adj = -0.5; } } apply_adj: #ifdef Avoid_Underflow if (scale && (y = word0(rv) & Exp_mask) <= 2*P*Exp_msk1) word0(adj) += (2*P+1)*Exp_msk1 - y; #else #ifdef Sudden_Underflow if ((word0(rv) & Exp_mask) <= P*Exp_msk1) { word0(rv) += P*Exp_msk1; dval(rv) += adj*ulp(dval(rv)); word0(rv) -= P*Exp_msk1; } else #endif /*Sudden_Underflow*/ #endif /*Avoid_Underflow*/ dval(rv) += adj*ulp(dval(rv)); } break; } adj = ratio(delta, bs); if (adj < 1.) adj = 1.; if (adj <= 0x7ffffffe) { /* adj = rounding ? ceil(adj) : floor(adj); */ y = adj; if (y != adj) { if (!((rounding>>1) ^ dsign)) y++; adj = y; } } #ifdef Avoid_Underflow if (scale && (y = word0(rv) & Exp_mask) <= 2*P*Exp_msk1) word0(adj) += (2*P+1)*Exp_msk1 - y; #else #ifdef Sudden_Underflow if ((word0(rv) & Exp_mask) <= P*Exp_msk1) { word0(rv) += P*Exp_msk1; adj *= ulp(dval(rv)); if (dsign) dval(rv) += adj; else dval(rv) -= adj; word0(rv) -= P*Exp_msk1; goto cont; } #endif /*Sudden_Underflow*/ #endif /*Avoid_Underflow*/ adj *= ulp(dval(rv)); if (dsign) dval(rv) += adj; else dval(rv) -= adj; goto cont; } #endif /*Honor_FLT_ROUNDS*/ if (i < 0) { /* Error is less than half an ulp -- check for * special case of mantissa a power of two. */ if (dsign || word1(rv) || word0(rv) & Bndry_mask #ifdef IEEE_Arith #ifdef Avoid_Underflow || (word0(rv) & Exp_mask) <= (2*P+1)*Exp_msk1 #else || (word0(rv) & Exp_mask) <= Exp_msk1 #endif #endif ) { #ifdef SET_INEXACT if (!delta->x[0] && delta->wds <= 1) inexact = 0; #endif break; } if (!delta->x[0] && delta->wds <= 1) { /* exact result */ #ifdef SET_INEXACT inexact = 0; #endif break; } delta = lshift(delta,Log2P); if (cmp(delta, bs) > 0) goto drop_down; break; } if (i == 0) { /* exactly half-way between */ if (dsign) { if ((word0(rv) & Bndry_mask1) == Bndry_mask1 && word1(rv) == ( #ifdef Avoid_Underflow (scale && (y = word0(rv) & Exp_mask) <= 2*P*Exp_msk1) ? (0xffffffff & (0xffffffff << (2*P+1-(y>>Exp_shift)))) : #endif 0xffffffff)) { /*boundary case -- increment exponent*/ word0(rv) = (word0(rv) & Exp_mask) + Exp_msk1 #ifdef IBM | Exp_msk1 >> 4 #endif ; word1(rv) = 0; #ifdef Avoid_Underflow dsign = 0; #endif break; } } else if (!(word0(rv) & Bndry_mask) && !word1(rv)) { drop_down: /* boundary case -- decrement exponent */ #ifdef Sudden_Underflow /*{{*/ L = word0(rv) & Exp_mask; #ifdef IBM if (L < Exp_msk1) #else #ifdef Avoid_Underflow if (L <= (scale ? (2*P+1)*Exp_msk1 : Exp_msk1)) #else if (L <= Exp_msk1) #endif /*Avoid_Underflow*/ #endif /*IBM*/ goto undfl; L -= Exp_msk1; #else /*Sudden_Underflow}{*/ #ifdef Avoid_Underflow if (scale) { L = word0(rv) & Exp_mask; if (L <= (2*P+1)*Exp_msk1) { if (L > (P+2)*Exp_msk1) /* round even ==> */ /* accept rv */ break; /* rv = smallest denormal */ goto undfl; } } #endif /*Avoid_Underflow*/ L = (word0(rv) & Exp_mask) - Exp_msk1; #endif /*Sudden_Underflow}}*/ word0(rv) = L | Bndry_mask1; word1(rv) = 0xffffffff; #ifdef IBM goto cont; #else break; #endif } #ifndef ROUND_BIASED if (!(word1(rv) & LSB)) break; #endif if (dsign) dval(rv) += ulp(dval(rv)); #ifndef ROUND_BIASED else { dval(rv) -= ulp(dval(rv)); #ifndef Sudden_Underflow if (!dval(rv)) goto undfl; #endif } #ifdef Avoid_Underflow dsign = 1 - dsign; #endif #endif break; } if ((aadj = ratio(delta, bs)) <= 2.) { if (dsign) aadj = aadj1 = 1.; else if (word1(rv) || word0(rv) & Bndry_mask) { #ifndef Sudden_Underflow if (word1(rv) == Tiny1 && !word0(rv)) goto undfl; #endif aadj = 1.; aadj1 = -1.; } else { /* special case -- power of FLT_RADIX to be */ /* rounded down... */ if (aadj < 2./FLT_RADIX) aadj = 1./FLT_RADIX; else aadj *= 0.5; aadj1 = -aadj; } } else { aadj *= 0.5; aadj1 = dsign ? aadj : -aadj; #ifdef Check_FLT_ROUNDS switch (Rounding) { case 2: /* towards +infinity */ aadj1 -= 0.5; break; case 0: /* towards 0 */ case 3: /* towards -infinity */ aadj1 += 0.5; } #else if (Flt_Rounds == 0) aadj1 += 0.5; #endif /*Check_FLT_ROUNDS*/ } y = word0(rv) & Exp_mask; /* Check for overflow */ if (y == Exp_msk1*(DBL_MAX_EXP+Bias-1)) { dval(rv0) = dval(rv); word0(rv) -= P*Exp_msk1; adj = aadj1 * ulp(dval(rv)); dval(rv) += adj; if ((word0(rv) & Exp_mask) >= Exp_msk1*(DBL_MAX_EXP+Bias-P)) { if (word0(rv0) == Big0 && word1(rv0) == Big1) goto ovfl; word0(rv) = Big0; word1(rv) = Big1; goto cont; } else word0(rv) += P*Exp_msk1; } else { #ifdef Avoid_Underflow if (scale && y <= 2*P*Exp_msk1) { if (aadj <= 0x7fffffff) { if ((z = aadj) <= 0) z = 1; aadj = z; aadj1 = dsign ? aadj : -aadj; } word0(aadj1) += (2*P+1)*Exp_msk1 - y; } adj = aadj1 * ulp(dval(rv)); dval(rv) += adj; #else #ifdef Sudden_Underflow if ((word0(rv) & Exp_mask) <= P*Exp_msk1) { dval(rv0) = dval(rv); word0(rv) += P*Exp_msk1; adj = aadj1 * ulp(dval(rv)); dval(rv) += adj; #ifdef IBM if ((word0(rv) & Exp_mask) < P*Exp_msk1) #else if ((word0(rv) & Exp_mask) <= P*Exp_msk1) #endif { if (word0(rv0) == Tiny0 && word1(rv0) == Tiny1) goto undfl; word0(rv) = Tiny0; word1(rv) = Tiny1; goto cont; } else word0(rv) -= P*Exp_msk1; } else { adj = aadj1 * ulp(dval(rv)); dval(rv) += adj; } #else /*Sudden_Underflow*/ /* Compute adj so that the IEEE rounding rules will * correctly round rv + adj in some half-way cases. * If rv * ulp(rv) is denormalized (i.e., * y <= (P-1)*Exp_msk1), we must adjust aadj to avoid * trouble from bits lost to denormalization; * example: 1.2e-307 . */ if (y <= (P-1)*Exp_msk1 && aadj > 1.) { aadj1 = (double)(int)(aadj + 0.5); if (!dsign) aadj1 = -aadj1; } adj = aadj1 * ulp(dval(rv)); dval(rv) += adj; #endif /*Sudden_Underflow*/ #endif /*Avoid_Underflow*/ } z = word0(rv) & Exp_mask; #ifndef SET_INEXACT #ifdef Avoid_Underflow if (!scale) #endif if (y == z) { /* Can we stop now? */ L = (Long)aadj; aadj -= L; /* The tolerances below are conservative. */ if (dsign || word1(rv) || word0(rv) & Bndry_mask) { if (aadj < .4999999 || aadj > .5000001) break; } else if (aadj < .4999999/FLT_RADIX) break; } #endif cont: Bfree(bb); Bfree(bd); Bfree(bs); Bfree(delta); } #ifdef SET_INEXACT if (inexact) { if (!oldinexact) { word0(rv0) = Exp_1 + (70 << Exp_shift); word1(rv0) = 0; dval(rv0) += 1.; } } else if (!oldinexact) clear_inexact(); #endif #ifdef Avoid_Underflow if (scale) { word0(rv0) = Exp_1 - 2*P*Exp_msk1; word1(rv0) = 0; dval(rv) *= dval(rv0); #ifndef NO_ERRNO /* try to avoid the bug of testing an 8087 register value */ if (word0(rv) == 0 && word1(rv) == 0) errno = ERANGE; #endif } #endif /* Avoid_Underflow */ #ifdef SET_INEXACT if (inexact && !(word0(rv) & Exp_mask)) { /* set underflow bit */ dval(rv0) = 1e-300; dval(rv0) *= dval(rv0); } #endif retfree: Bfree(bb); Bfree(bd); Bfree(bs); Bfree(bd0); Bfree(delta); ret: if (se) *se = (char *)s; return sign ? -dval(rv) : dval(rv); } static int quorem(Bigint *b, Bigint *S) { int n; ULong *bx, *bxe, q, *sx, *sxe; #ifdef ULLong ULLong borrow, carry, y, ys; #else ULong borrow, carry, y, ys; #ifdef Pack_32 ULong si, z, zs; #endif #endif n = S->wds; #ifdef DEBUG /*debug*/ if (b->wds > n) /*debug*/ Bug("oversize b in quorem"); #endif if (b->wds < n) return 0; sx = S->x; sxe = sx + --n; bx = b->x; bxe = bx + n; q = *bxe / (*sxe + 1); /* ensure q <= true quotient */ #ifdef DEBUG /*debug*/ if (q > 9) /*debug*/ Bug("oversized quotient in quorem"); #endif if (q) { borrow = 0; carry = 0; do { #ifdef ULLong ys = *sx++ * (ULLong)q + carry; carry = ys >> 32; y = *bx - (ys & FFFFFFFF) - borrow; borrow = y >> 32 & (ULong)1; *bx++ = y & FFFFFFFF; #else #ifdef Pack_32 si = *sx++; ys = (si & 0xffff) * q + carry; zs = (si >> 16) * q + (ys >> 16); carry = zs >> 16; y = (*bx & 0xffff) - (ys & 0xffff) - borrow; borrow = (y & 0x10000) >> 16; z = (*bx >> 16) - (zs & 0xffff) - borrow; borrow = (z & 0x10000) >> 16; Storeinc(bx, z, y); #else ys = *sx++ * q + carry; carry = ys >> 16; y = *bx - (ys & 0xffff) - borrow; borrow = (y & 0x10000) >> 16; *bx++ = y & 0xffff; #endif #endif } while (sx <= sxe); if (!*bxe) { bx = b->x; while (--bxe > bx && !*bxe) --n; b->wds = n; } } if (cmp(b, S) >= 0) { q++; borrow = 0; carry = 0; bx = b->x; sx = S->x; do { #ifdef ULLong ys = *sx++ + carry; carry = ys >> 32; y = *bx - (ys & FFFFFFFF) - borrow; borrow = y >> 32 & (ULong)1; *bx++ = y & FFFFFFFF; #else #ifdef Pack_32 si = *sx++; ys = (si & 0xffff) + carry; zs = (si >> 16) + (ys >> 16); carry = zs >> 16; y = (*bx & 0xffff) - (ys & 0xffff) - borrow; borrow = (y & 0x10000) >> 16; z = (*bx >> 16) - (zs & 0xffff) - borrow; borrow = (z & 0x10000) >> 16; Storeinc(bx, z, y); #else ys = *sx++ + carry; carry = ys >> 16; y = *bx - (ys & 0xffff) - borrow; borrow = (y & 0x10000) >> 16; *bx++ = y & 0xffff; #endif #endif } while (sx <= sxe); bx = b->x; bxe = bx + n; if (!*bxe) { while (--bxe > bx && !*bxe) --n; b->wds = n; } } return q; } #ifndef MULTIPLE_THREADS static char *dtoa_result; #endif static char * rv_alloc(int i) { int j, k, *r; j = sizeof(ULong); for (k = 0; sizeof(Bigint) - sizeof(ULong) - sizeof(int) + j <= i; j <<= 1) k++; r = (int*)Balloc(k); *r = k; return #ifndef MULTIPLE_THREADS dtoa_result = #endif (char *)(r+1); } static char * nrv_alloc(char *s, char **rve, int n) { char *rv, *t; t = rv = rv_alloc(n); while ((*t = *s++) != 0) t++; if (rve) *rve = t; return rv; } /* freedtoa(s) must be used to free values s returned by dtoa * when MULTIPLE_THREADS is #defined. It should be used in all cases, * but for consistency with earlier versions of dtoa, it is optional * when MULTIPLE_THREADS is not defined. */ void freedtoa(char *s) { Bigint *b = (Bigint *)((int *)s - 1); b->maxwds = 1 << (b->k = *(int*)b); Bfree(b); #ifndef MULTIPLE_THREADS if (s == dtoa_result) dtoa_result = 0; #endif } /* dtoa for IEEE arithmetic (dmg): convert double to ASCII string. * * Inspired by "How to Print Floating-Point Numbers Accurately" by * Guy L. Steele, Jr. and Jon L. White [Proc. ACM SIGPLAN '90, pp. 112-126]. * * Modifications: * 1. Rather than iterating, we use a simple numeric overestimate * to determine k = floor(log10(d)). We scale relevant * quantities using O(log2(k)) rather than O(k) multiplications. * 2. For some modes > 2 (corresponding to ecvt and fcvt), we don't * try to generate digits strictly left to right. Instead, we * compute with fewer bits and propagate the carry if necessary * when rounding the final digit up. This is often faster. * 3. Under the assumption that input will be rounded nearest, * mode 0 renders 1e23 as 1e23 rather than 9.999999999999999e22. * That is, we allow equality in stopping tests when the * round-nearest rule will give the same floating-point value * as would satisfaction of the stopping test with strict * inequality. * 4. We remove common factors of powers of 2 from relevant * quantities. * 5. When converting floating-point integers less than 1e16, * we use floating-point arithmetic rather than resorting * to multiple-precision integers. * 6. When asked to produce fewer than 15 digits, we first try * to get by with floating-point arithmetic; we resort to * multiple-precision integer arithmetic only if we cannot * guarantee that the floating-point calculation has given * the correctly rounded result. For k requested digits and * "uniformly" distributed input, the probability is * something like 10^(k-15) that we must resort to the Long * calculation. */ char * dtoa(double d, int mode, int ndigits, int *decpt, int *sign, char **rve) { /* Arguments ndigits, decpt, sign are similar to those of ecvt and fcvt; trailing zeros are suppressed from the returned string. If not null, *rve is set to point to the end of the return value. If d is +-Infinity or NaN, then *decpt is set to 9999. mode: 0 ==> shortest string that yields d when read in and rounded to nearest. 1 ==> like 0, but with Steele & White stopping rule; e.g. with IEEE P754 arithmetic , mode 0 gives 1e23 whereas mode 1 gives 9.999999999999999e22. 2 ==> max(1,ndigits) significant digits. This gives a return value similar to that of ecvt, except that trailing zeros are suppressed. 3 ==> through ndigits past the decimal point. This gives a return value similar to that from fcvt, except that trailing zeros are suppressed, and ndigits can be negative. 4,5 ==> similar to 2 and 3, respectively, but (in round-nearest mode) with the tests of mode 0 to possibly return a shorter string that rounds to d. With IEEE arithmetic and compilation with -DHonor_FLT_ROUNDS, modes 4 and 5 behave the same as modes 2 and 3 when FLT_ROUNDS != 1. 6-9 ==> Debugging modes similar to mode - 4: don't try fast floating-point estimate (if applicable). Values of mode other than 0-9 are treated as mode 0. Sufficient space is allocated to the return value to hold the suppressed trailing zeros. */ int bbits, b2, b5, be, dig, i, ieps, ilim, ilim0, ilim1, j, j1, k, k0, k_check, leftright, m2, m5, s2, s5, spec_case, try_quick; Long L; #ifndef Sudden_Underflow int denorm; ULong x; #endif Bigint *b, *b1, *delta, *mlo, *mhi, *S; double d2, ds, eps; char *s, *s0; #ifdef Honor_FLT_ROUNDS int rounding; #endif #ifdef SET_INEXACT int inexact, oldinexact; #endif #ifndef MULTIPLE_THREADS if (dtoa_result) { freedtoa(dtoa_result); dtoa_result = 0; } #endif if (word0(d) & Sign_bit) { /* set sign for everything, including 0's and NaNs */ *sign = 1; word0(d) &= ~Sign_bit; /* clear sign bit */ } else *sign = 0; #if defined(IEEE_Arith) + defined(VAX) #ifdef IEEE_Arith if ((word0(d) & Exp_mask) == Exp_mask) #else if (word0(d) == 0x8000) #endif { /* Infinity or NaN */ *decpt = 9999; #ifdef IEEE_Arith if (!word1(d) && !(word0(d) & 0xfffff)) return nrv_alloc("Infinity", rve, 8); #endif return nrv_alloc("NaN", rve, 3); } #endif #ifdef IBM dval(d) += 0; /* normalize */ #endif if (!dval(d)) { *decpt = 1; return nrv_alloc("0", rve, 1); } #ifdef SET_INEXACT try_quick = oldinexact = get_inexact(); inexact = 1; #endif #ifdef Honor_FLT_ROUNDS if ((rounding = Flt_Rounds) >= 2) { if (*sign) rounding = rounding == 2 ? 0 : 2; else if (rounding != 2) rounding = 0; } #endif b = d2b(dval(d), &be, &bbits); #ifdef Sudden_Underflow i = (int)(word0(d) >> Exp_shift1 & (Exp_mask>>Exp_shift1)); #else if ((i = (int)(word0(d) >> Exp_shift1 & (Exp_mask>>Exp_shift1))) != 0) { #endif dval(d2) = dval(d); word0(d2) &= Frac_mask1; word0(d2) |= Exp_11; #ifdef IBM if (j = 11 - hi0bits(word0(d2) & Frac_mask)) dval(d2) /= 1 << j; #endif /* log(x) ~=~ log(1.5) + (x-1.5)/1.5 * log10(x) = log(x) / log(10) * ~=~ log(1.5)/log(10) + (x-1.5)/(1.5*log(10)) * log10(d) = (i-Bias)*log(2)/log(10) + log10(d2) * * This suggests computing an approximation k to log10(d) by * * k = (i - Bias)*0.301029995663981 * + ( (d2-1.5)*0.289529654602168 + 0.176091259055681 ); * * We want k to be too large rather than too small. * The error in the first-order Taylor series approximation * is in our favor, so we just round up the constant enough * to compensate for any error in the multiplication of * (i - Bias) by 0.301029995663981; since |i - Bias| <= 1077, * and 1077 * 0.30103 * 2^-52 ~=~ 7.2e-14, * adding 1e-13 to the constant term more than suffices. * Hence we adjust the constant term to 0.1760912590558. * (We could get a more accurate k by invoking log10, * but this is probably not worthwhile.) */ i -= Bias; #ifdef IBM i <<= 2; i += j; #endif #ifndef Sudden_Underflow denorm = 0; } else { /* d is denormalized */ i = bbits + be + (Bias + (P-1) - 1); x = i > 32 ? word0(d) << (64 - i) | word1(d) >> (i - 32) : word1(d) << (32 - i); dval(d2) = x; word0(d2) -= 31*Exp_msk1; /* adjust exponent */ i -= (Bias + (P-1) - 1) + 1; denorm = 1; } #endif ds = (dval(d2)-1.5)*0.289529654602168 + 0.1760912590558 + i*0.301029995663981; k = (int)ds; if (ds < 0. && ds != k) k--; /* want k = floor(ds) */ k_check = 1; if (k >= 0 && k <= Ten_pmax) { if (dval(d) < tens[k]) k--; k_check = 0; } j = bbits - i - 1; if (j >= 0) { b2 = 0; s2 = j; } else { b2 = -j; s2 = 0; } if (k >= 0) { b5 = 0; s5 = k; s2 += k; } else { b2 -= k; b5 = -k; s5 = 0; } if (mode < 0 || mode > 9) mode = 0; #ifndef SET_INEXACT #ifdef Check_FLT_ROUNDS try_quick = Rounding == 1; #else try_quick = 1; #endif #endif /*SET_INEXACT*/ if (mode > 5) { mode -= 4; try_quick = 0; } leftright = 1; ilim = ilim1 = -1; switch (mode) { case 0: case 1: i = 18; ndigits = 0; break; case 2: leftright = 0; /* no break */ case 4: if (ndigits <= 0) ndigits = 1; ilim = ilim1 = i = ndigits; break; case 3: leftright = 0; /* no break */ case 5: i = ndigits + k + 1; ilim = i; ilim1 = i - 1; if (i <= 0) i = 1; } s = s0 = rv_alloc(i+1); #ifdef Honor_FLT_ROUNDS if (mode > 1 && rounding != 1) leftright = 0; #endif if (ilim >= 0 && ilim <= Quick_max && try_quick) { /* Try to get by with floating-point arithmetic. */ i = 0; dval(d2) = dval(d); k0 = k; ilim0 = ilim; ieps = 2; /* conservative */ if (k > 0) { ds = tens[k&0xf]; j = k >> 4; if (j & Bletch) { /* prevent overflows */ j &= Bletch - 1; dval(d) /= bigtens[n_bigtens-1]; ieps++; } for (; j; j >>= 1, i++) if (j & 1) { ieps++; ds *= bigtens[i]; } dval(d) /= ds; } else if ((j1 = -k) != 0) { dval(d) *= tens[j1 & 0xf]; for (j = j1 >> 4; j; j >>= 1, i++) if (j & 1) { ieps++; dval(d) *= bigtens[i]; } } if (k_check && dval(d) < 1. && ilim > 0) { if (ilim1 <= 0) goto fast_failed; ilim = ilim1; k--; dval(d) *= 10.; ieps++; } dval(eps) = ieps*dval(d) + 7.; word0(eps) -= (P-1)*Exp_msk1; if (ilim == 0) { S = mhi = 0; dval(d) -= 5.; if (dval(d) > dval(eps)) goto one_digit; if (dval(d) < -dval(eps)) goto no_digits; goto fast_failed; } #ifndef No_leftright if (leftright) { /* Use Steele & White method of only * generating digits needed. */ dval(eps) = 0.5/tens[ilim-1] - dval(eps); for (i = 0;;) { L = dval(d); dval(d) -= L; *s++ = '0' + (int)L; if (dval(d) < dval(eps)) goto ret1; if (1. - dval(d) < dval(eps)) goto bump_up; if (++i >= ilim) break; dval(eps) *= 10.; dval(d) *= 10.; } } else { #endif /* Generate ilim digits, then fix them up. */ dval(eps) *= tens[ilim-1]; for (i = 1;; i++, dval(d) *= 10.) { L = (Long)(dval(d)); if (!(dval(d) -= L)) ilim = i; *s++ = '0' + (int)L; if (i == ilim) { if (dval(d) > 0.5 + dval(eps)) goto bump_up; else if (dval(d) < 0.5 - dval(eps)) { while (*--s == '0') ; s++; goto ret1; } break; } } #ifndef No_leftright } #endif fast_failed: s = s0; dval(d) = dval(d2); k = k0; ilim = ilim0; } /* Do we have a "small" integer? */ if (be >= 0 && k <= Int_max) { /* Yes. */ ds = tens[k]; if (ndigits < 0 && ilim <= 0) { S = mhi = 0; if (ilim < 0 || dval(d) <= 5*ds) goto no_digits; goto one_digit; } for (i = 1;; i++, dval(d) *= 10.) { L = (Long)(dval(d) / ds); dval(d) -= L*ds; #ifdef Check_FLT_ROUNDS /* If FLT_ROUNDS == 2, L will usually be high by 1 */ if (dval(d) < 0) { L--; dval(d) += ds; } #endif *s++ = '0' + (int)L; if (!dval(d)) { #ifdef SET_INEXACT inexact = 0; #endif break; } if (i == ilim) { #ifdef Honor_FLT_ROUNDS if (mode > 1) switch (rounding) { case 0: goto ret1; case 2: goto bump_up; } #endif dval(d) += dval(d); if (dval(d) > ds || (dval(d) == ds && (L & 1))) { bump_up: while (*--s == '9') if (s == s0) { k++; *s = '0'; break; } ++*s++; } break; } } goto ret1; } m2 = b2; m5 = b5; mhi = mlo = 0; if (leftright) { i = #ifndef Sudden_Underflow denorm ? be + (Bias + (P-1) - 1 + 1) : #endif #ifdef IBM 1 + 4*P - 3 - bbits + ((bbits + be - 1) & 3); #else 1 + P - bbits; #endif b2 += i; s2 += i; mhi = i2b(1); } if (m2 > 0 && s2 > 0) { i = m2 < s2 ? m2 : s2; b2 -= i; m2 -= i; s2 -= i; } if (b5 > 0) { if (leftright) { if (m5 > 0) { mhi = pow5mult(mhi, m5); b1 = mult(mhi, b); Bfree(b); b = b1; } if ((j = b5 - m5) != 0) b = pow5mult(b, j); } else b = pow5mult(b, b5); } S = i2b(1); if (s5 > 0) S = pow5mult(S, s5); /* Check for special case that d is a normalized power of 2. */ spec_case = 0; if ((mode < 2 || leftright) #ifdef Honor_FLT_ROUNDS && rounding == 1 #endif ) { if (!word1(d) && !(word0(d) & Bndry_mask) #ifndef Sudden_Underflow && word0(d) & (Exp_mask & ~Exp_msk1) #endif ) { /* The special case */ b2 += Log2P; s2 += Log2P; spec_case = 1; } } /* Arrange for convenient computation of quotients: * shift left if necessary so divisor has 4 leading 0 bits. * * Perhaps we should just compute leading 28 bits of S once * and for all and pass them and a shift to quorem, so it * can do shifts and ors to compute the numerator for q. */ #ifdef Pack_32 if ((i = ((s5 ? 32 - hi0bits(S->x[S->wds-1]) : 1) + s2) & 0x1f) != 0) i = 32 - i; #else if ((i = ((s5 ? 32 - hi0bits(S->x[S->wds-1]) : 1) + s2) & 0xf) != 0) i = 16 - i; #endif if (i > 4) { i -= 4; b2 += i; m2 += i; s2 += i; } else if (i < 4) { i += 28; b2 += i; m2 += i; s2 += i; } if (b2 > 0) b = lshift(b, b2); if (s2 > 0) S = lshift(S, s2); if (k_check) { if (cmp(b,S) < 0) { k--; b = multadd(b, 10, 0); /* we botched the k estimate */ if (leftright) mhi = multadd(mhi, 10, 0); ilim = ilim1; } } if (ilim <= 0 && (mode == 3 || mode == 5)) { if (ilim < 0 || cmp(b,S = multadd(S,5,0)) <= 0) { /* no digits, fcvt style */ no_digits: k = -1 - ndigits; goto ret; } one_digit: *s++ = '1'; k++; goto ret; } if (leftright) { if (m2 > 0) mhi = lshift(mhi, m2); /* Compute mlo -- check for special case * that d is a normalized power of 2. */ mlo = mhi; if (spec_case) { mhi = Balloc(mhi->k); Bcopy(mhi, mlo); mhi = lshift(mhi, Log2P); } for (i = 1;;i++) { dig = quorem(b,S) + '0'; /* Do we yet have the shortest decimal string * that will round to d? */ j = cmp(b, mlo); delta = diff(S, mhi); j1 = delta->sign ? 1 : cmp(b, delta); Bfree(delta); #ifndef ROUND_BIASED if (j1 == 0 && mode != 1 && !(word1(d) & 1) #ifdef Honor_FLT_ROUNDS && rounding >= 1 #endif ) { if (dig == '9') goto round_9_up; if (j > 0) dig++; #ifdef SET_INEXACT else if (!b->x[0] && b->wds <= 1) inexact = 0; #endif *s++ = dig; goto ret; } #endif if (j < 0 || (j == 0 && mode != 1 #ifndef ROUND_BIASED && !(word1(d) & 1) #endif )) { if (!b->x[0] && b->wds <= 1) { #ifdef SET_INEXACT inexact = 0; #endif goto accept_dig; } #ifdef Honor_FLT_ROUNDS if (mode > 1) switch (rounding) { case 0: goto accept_dig; case 2: goto keep_dig; } #endif /*Honor_FLT_ROUNDS*/ if (j1 > 0) { b = lshift(b, 1); j1 = cmp(b, S); if ((j1 > 0 || (j1 == 0 && (dig & 1))) && dig++ == '9') goto round_9_up; } accept_dig: *s++ = dig; goto ret; } if (j1 > 0) { #ifdef Honor_FLT_ROUNDS if (!rounding) goto accept_dig; #endif if (dig == '9') { /* possible if i == 1 */ round_9_up: *s++ = '9'; goto roundoff; } *s++ = dig + 1; goto ret; } #ifdef Honor_FLT_ROUNDS keep_dig: #endif *s++ = dig; if (i == ilim) break; b = multadd(b, 10, 0); if (mlo == mhi) mlo = mhi = multadd(mhi, 10, 0); else { mlo = multadd(mlo, 10, 0); mhi = multadd(mhi, 10, 0); } } } else for (i = 1;; i++) { *s++ = dig = quorem(b,S) + '0'; if (!b->x[0] && b->wds <= 1) { #ifdef SET_INEXACT inexact = 0; #endif goto ret; } if (i >= ilim) break; b = multadd(b, 10, 0); } /* Round off last digit */ #ifdef Honor_FLT_ROUNDS switch (rounding) { case 0: goto trimzeros; case 2: goto roundoff; } #endif b = lshift(b, 1); j = cmp(b, S); if (j > 0 || (j == 0 && (dig & 1))) { roundoff: while (*--s == '9') if (s == s0) { k++; *s++ = '1'; goto ret; } ++*s++; } else { while (*--s == '0') ; s++; } ret: Bfree(S); if (mhi) { if (mlo && mlo != mhi) Bfree(mlo); Bfree(mhi); } ret1: #ifdef SET_INEXACT if (inexact) { if (!oldinexact) { word0(d) = Exp_1 + (70 << Exp_shift); word1(d) = 0; dval(d) += 1.; } } else if (!oldinexact) clear_inexact(); #endif Bfree(b); *s = 0; *decpt = k + 1; if (rve) *rve = s; return s0; } void ruby_each_words(const char *str, void (*func)(const char*, int, void*), void *arg) { const char *end; int len; if (!str) return; for (; *str; str = end) { while (ISSPACE(*str) || *str == ',') str++; if (!*str) break; end = str; while (*end && !ISSPACE(*end) && *end != ',') end++; len = end - str; (*func)(str, len, arg); } } #ifdef __cplusplus } #endif /********************************************************************** variable.c - $Author: shyouhei $ $Date: 2009-02-02 03:34:12 +0100 (Mon, 02 Feb 2009) $ created at: Tue Apr 19 23:55:15 JST 1994 Copyright (C) 1993-2003 Yukihiro Matsumoto Copyright (C) 2000 Network Applied Communication Laboratory, Inc. Copyright (C) 2000 Information-technology Promotion Agency, Japan **********************************************************************/ #include "ruby.h" #include "env.h" #include "node.h" #include "st.h" #include "util.h" st_table *rb_global_tbl; st_table *rb_class_tbl; static ID autoload, classpath, tmp_classpath; void Init_var_tables() { rb_global_tbl = st_init_numtable(); rb_class_tbl = st_init_numtable(); autoload = rb_intern("__autoload__"); classpath = rb_intern("__classpath__"); tmp_classpath = rb_intern("__tmp_classpath__"); } struct fc_result { ID name; VALUE klass; VALUE path; VALUE track; struct fc_result *prev; }; static VALUE fc_path(fc, name) struct fc_result *fc; ID name; { VALUE path, tmp; path = rb_str_new2(rb_id2name(name)); while (fc) { if (fc->track == rb_cObject) break; if (ROBJECT(fc->track)->iv_tbl && st_lookup(ROBJECT(fc->track)->iv_tbl, classpath, &tmp)) { tmp = rb_str_dup(tmp); rb_str_cat2(tmp, "::"); rb_str_append(tmp, path); return tmp; } tmp = rb_str_new2(rb_id2name(fc->name)); rb_str_cat2(tmp, "::"); rb_str_append(tmp, path); path = tmp; fc = fc->prev; } return path; } static int fc_i(key, value, res) ID key; VALUE value; struct fc_result *res; { if (!rb_is_const_id(key)) return ST_CONTINUE; if (value == res->klass) { res->path = fc_path(res, key); return ST_STOP; } switch (TYPE(value)) { case T_MODULE: case T_CLASS: if (!RCLASS(value)->iv_tbl) return ST_CONTINUE; else { struct fc_result arg; struct fc_result *list; list = res; while (list) { if (list->track == value) return ST_CONTINUE; list = list->prev; } arg.name = key; arg.path = 0; arg.klass = res->klass; arg.track = value; arg.prev = res; st_foreach_safe(RCLASS(value)->iv_tbl, fc_i, (st_data_t)&arg); if (arg.path) { res->path = arg.path; return ST_STOP; } } break; default: break; } return ST_CONTINUE; } static VALUE find_class_path(klass) VALUE klass; { struct fc_result arg; arg.name = 0; arg.path = 0; arg.klass = klass; arg.track = rb_cObject; arg.prev = 0; if (RCLASS(rb_cObject)->iv_tbl) { st_foreach_safe(RCLASS(rb_cObject)->iv_tbl, fc_i, (st_data_t)&arg); } if (arg.path == 0) { st_foreach(rb_class_tbl, fc_i, (st_data_t)&arg); } if (arg.path) { if (!ROBJECT(klass)->iv_tbl) { ROBJECT(klass)->iv_tbl = st_init_numtable(); } st_insert(ROBJECT(klass)->iv_tbl, classpath, arg.path); st_delete(RCLASS(klass)->iv_tbl, &tmp_classpath, 0); return arg.path; } return Qnil; } static VALUE classname(klass) VALUE klass; { VALUE path = Qnil; if (!klass) klass = rb_cObject; if (ROBJECT(klass)->iv_tbl) { if (!st_lookup(ROBJECT(klass)->iv_tbl, classpath, &path)) { ID classid = rb_intern("__classid__"); if (!st_lookup(ROBJECT(klass)->iv_tbl, classid, &path)) { return find_class_path(klass); } path = rb_str_new2(rb_id2name(SYM2ID(path))); st_insert(ROBJECT(klass)->iv_tbl, classpath, path); st_delete(RCLASS(klass)->iv_tbl, (st_data_t*)&classid, 0); } if (TYPE(path) != T_STRING) { rb_bug("class path is not set properly"); } return path; } return find_class_path(klass); } /* * call-seq: * mod.name => string * * Returns the name of the module mod. */ VALUE rb_mod_name(mod) VALUE mod; { VALUE path = classname(mod); if (!NIL_P(path)) return rb_str_dup(path); return rb_str_new(0,0); } VALUE rb_class_path(klass) VALUE klass; { VALUE path = classname(klass); if (!NIL_P(path)) return path; if (RCLASS(klass)->iv_tbl && st_lookup(RCLASS(klass)->iv_tbl, tmp_classpath, &path)) { return path; } else { char *s = "Class"; size_t len; if (TYPE(klass) == T_MODULE) { if (rb_obj_class(klass) == rb_cModule) { s = "Module"; } else { s = rb_class2name(RBASIC(klass)->klass); } } len = 2 + strlen(s) + 3 + 2 * SIZEOF_LONG + 1; path = rb_str_new(0, len); snprintf(RSTRING(path)->ptr, len+1, "#<%s:0x%lx>", s, klass); RSTRING(path)->len = strlen(RSTRING(path)->ptr); rb_ivar_set(klass, tmp_classpath, path); return path; } } void rb_set_class_path(klass, under, name) VALUE klass, under; const char *name; { VALUE str; if (under == rb_cObject) { str = rb_str_new2(name); } else { str = rb_str_dup(rb_class_path(under)); rb_str_cat2(str, "::"); rb_str_cat2(str, name); } rb_ivar_set(klass, classpath, str); } VALUE rb_path2class(path) const char *path; { const char *pbeg, *p; ID id; VALUE c = rb_cObject; VALUE str = 0; if (path[0] == '#') { rb_raise(rb_eArgError, "can't retrieve anonymous class %s", path); } pbeg = p = path; while (*p) { while (*p && *p != ':') p++; if (str) { RSTRING(str)->len = 0; rb_str_cat(str, pbeg, p-pbeg); } else { str = rb_str_new(pbeg, p-pbeg); } id = rb_intern(RSTRING(str)->ptr); if (p[0] == ':') { if (p[1] != ':') goto undefined_class; p += 2; pbeg = p; } if (!rb_const_defined(c, id)) { undefined_class: rb_raise(rb_eArgError, "undefined class/module %.*s", p-path, path); } c = rb_const_get_at(c, id); switch (TYPE(c)) { case T_MODULE: case T_CLASS: break; default: rb_raise(rb_eTypeError, "%s does not refer class/module", path); } } return c; } void rb_name_class(klass, id) VALUE klass; ID id; { rb_iv_set(klass, "__classid__", ID2SYM(id)); } VALUE rb_class_name(klass) VALUE klass; { return rb_class_path(rb_class_real(klass)); } char * rb_class2name(klass) VALUE klass; { return RSTRING(rb_class_name(klass))->ptr; } char * rb_obj_classname(obj) VALUE obj; { return rb_class2name(CLASS_OF(obj)); } struct trace_var { int removed; void (*func)(); VALUE data; struct trace_var *next; }; struct global_variable { int counter; void *data; VALUE (*getter)(); void (*setter)(); void (*marker)(); int block_trace; struct trace_var *trace; }; struct global_entry { struct global_variable *var; ID id; }; static VALUE undef_getter(); static void undef_setter(); static void undef_marker(); static VALUE val_getter(); static void val_setter(); static void val_marker(); static VALUE var_getter(); static void var_setter(); static void var_marker(); struct global_entry* rb_global_entry(id) ID id; { struct global_entry *entry; if (!st_lookup(rb_global_tbl, id, (st_data_t *)&entry)) { struct global_variable *var; entry = ALLOC(struct global_entry); var = ALLOC(struct global_variable); entry->id = id; entry->var = var; var->counter = 1; var->data = 0; var->getter = undef_getter; var->setter = undef_setter; var->marker = undef_marker; var->block_trace = 0; var->trace = 0; st_add_direct(rb_global_tbl, id, (st_data_t)entry); } return entry; } static VALUE undef_getter(id) ID id; { rb_warning("global variable `%s' not initialized", rb_id2name(id)); return Qnil; } static void undef_setter(val, id, data, var) VALUE val; ID id; void *data; struct global_variable *var; { var->getter = val_getter; var->setter = val_setter; var->marker = val_marker; var->data = (void*)val; } static void undef_marker() { } static VALUE val_getter(id, val) ID id; VALUE val; { return val; } static void val_setter(val, id, data, var) VALUE val; ID id; void *data; struct global_variable *var; { var->data = (void*)val; } static void val_marker(data) VALUE data; { if (data) rb_gc_mark_maybe(data); } static VALUE var_getter(id, var) ID id; VALUE *var; { if (!var) return Qnil; return *var; } static void var_setter(val, id, var) VALUE val; ID id; VALUE *var; { *var = val; } static void var_marker(var) VALUE *var; { if (var) rb_gc_mark_maybe(*var); } static void readonly_setter(val, id, var) VALUE val; ID id; void *var; { rb_name_error(id, "%s is a read-only variable", rb_id2name(id)); } static int mark_global_entry(key, entry) ID key; struct global_entry *entry; { struct trace_var *trace; struct global_variable *var = entry->var; (*var->marker)(var->data); trace = var->trace; while (trace) { if (trace->data) rb_gc_mark_maybe(trace->data); trace = trace->next; } return ST_CONTINUE; } void rb_gc_mark_global_tbl() { if (rb_global_tbl) { st_foreach(rb_global_tbl, mark_global_entry, 0); } } static ID global_id(name) const char *name; { ID id; if (name[0] == '$') id = rb_intern(name); else { char *buf = ALLOCA_N(char, strlen(name)+2); buf[0] = '$'; strcpy(buf+1, name); id = rb_intern(buf); } return id; } void rb_define_hooked_variable(name, var, getter, setter) const char *name; VALUE *var; VALUE (*getter)(); void (*setter)(); { struct global_variable *gvar; ID id = global_id(name); gvar = rb_global_entry(id)->var; gvar->data = (void*)var; gvar->getter = getter?getter:var_getter; gvar->setter = setter?setter:var_setter; gvar->marker = var_marker; } void rb_define_variable(name, var) const char *name; VALUE *var; { rb_define_hooked_variable(name, var, 0, 0); } void rb_define_readonly_variable(name, var) const char *name; VALUE *var; { rb_define_hooked_variable(name, var, 0, readonly_setter); } void rb_define_virtual_variable(name, getter, setter) const char *name; VALUE (*getter)(); void (*setter)(); { if (!getter) getter = val_getter; if (!setter) setter = readonly_setter; rb_define_hooked_variable(name, 0, getter, setter); } static void rb_trace_eval(cmd, val) VALUE cmd, val; { rb_eval_cmd(cmd, rb_ary_new3(1, val), 0); } /* * call-seq: * trace_var(symbol, cmd ) => nil * trace_var(symbol) {|val| block } => nil * * Controls tracing of assignments to global variables. The parameter * +symbol_ identifies the variable (as either a string name or a * symbol identifier). _cmd_ (which may be a string or a * +Proc+ object) or block is executed whenever the variable * is assigned. The block or +Proc+ object receives the * variable's new value as a parameter. Also see * Kernel::untrace_var. * * trace_var :$_, proc {|v| puts "$_ is now '#{v}'" } * $_ = "hello" * $_ = ' there' * * produces: * * $_ is now 'hello' * $_ is now ' there' */ VALUE rb_f_trace_var(argc, argv) int argc; VALUE *argv; { VALUE var, cmd; struct global_entry *entry; struct trace_var *trace; rb_secure(4); if (rb_scan_args(argc, argv, "11", &var, &cmd) == 1) { cmd = rb_block_proc(); } if (NIL_P(cmd)) { return rb_f_untrace_var(argc, argv); } entry = rb_global_entry(rb_to_id(var)); if (OBJ_TAINTED(cmd)) { rb_raise(rb_eSecurityError, "Insecure: tainted variable trace"); } trace = ALLOC(struct trace_var); trace->next = entry->var->trace; trace->func = rb_trace_eval; trace->data = cmd; trace->removed = 0; entry->var->trace = trace; return Qnil; } static void remove_trace(var) struct global_variable *var; { struct trace_var *trace = var->trace; struct trace_var t; struct trace_var *next; t.next = trace; trace = &t; while (trace->next) { next = trace->next; if (next->removed) { trace->next = next->next; free(next); } else { trace = next; } } var->trace = t.next; } /* * call-seq: * untrace_var(symbol [, cmd] ) => array or nil * * Removes tracing for the specified command on the given global * variable and returns +nil+. If no command is specified, * removes all tracing for that variable and returns an array * containing the commands actually removed. */ VALUE rb_f_untrace_var(argc, argv) int argc; VALUE *argv; { VALUE var, cmd; ID id; struct global_entry *entry; struct trace_var *trace; rb_secure(4); rb_scan_args(argc, argv, "11", &var, &cmd); id = rb_to_id(var); if (!st_lookup(rb_global_tbl, id, (st_data_t *)&entry)) { rb_name_error(id, "undefined global variable %s", rb_id2name(id)); } trace = entry->var->trace; if (NIL_P(cmd)) { VALUE ary = rb_ary_new(); while (trace) { struct trace_var *next = trace->next; rb_ary_push(ary, (VALUE)trace->data); trace->removed = 1; trace = next; } if (!entry->var->block_trace) remove_trace(entry->var); return ary; } else { while (trace) { if (trace->data == cmd) { trace->removed = 1; if (!entry->var->block_trace) remove_trace(entry->var); return rb_ary_new3(1, cmd); } trace = trace->next; } } return Qnil; } VALUE rb_gvar_get(entry) struct global_entry *entry; { struct global_variable *var = entry->var; return (*var->getter)(entry->id, var->data, var); } struct trace_data { struct trace_var *trace; VALUE val; }; static VALUE trace_ev(data) struct trace_data *data; { struct trace_var *trace = data->trace; while (trace) { (*trace->func)(trace->data, data->val); trace = trace->next; } return Qnil; /* not reached */ } static VALUE trace_en(var) struct global_variable *var; { var->block_trace = 0; remove_trace(var); return Qnil; /* not reached */ } VALUE rb_gvar_set(entry, val) struct global_entry *entry; VALUE val; { struct trace_data trace; struct global_variable *var = entry->var; if (rb_safe_level() >= 4) rb_raise(rb_eSecurityError, "Insecure: can't change global variable value"); (*var->setter)(val, entry->id, var->data, var); if (var->trace && !var->block_trace) { var->block_trace = 1; trace.trace = var->trace; trace.val = val; rb_ensure(trace_ev, (VALUE)&trace, trace_en, (VALUE)var); } return val; } VALUE rb_gv_set(name, val) const char *name; VALUE val; { struct global_entry *entry; entry = rb_global_entry(global_id(name)); return rb_gvar_set(entry, val); } VALUE rb_gv_get(name) const char *name; { struct global_entry *entry; entry = rb_global_entry(global_id(name)); return rb_gvar_get(entry); } VALUE rb_gvar_defined(entry) struct global_entry *entry; { if (entry->var->getter == undef_getter) return Qfalse; return Qtrue; } static int gvar_i(key, entry, ary) ID key; struct global_entry *entry; VALUE ary; { rb_ary_push(ary, rb_str_new2(rb_id2name(key))); return ST_CONTINUE; } /* * call-seq: * global_variables => array * * Returns an array of the names of global variables. * * global_variables.grep /std/ #=> ["$stderr", "$stdout", "$stdin"] */ VALUE rb_f_global_variables() { VALUE ary = rb_ary_new(); char buf[4]; char *s = "&`'+123456789"; st_foreach(rb_global_tbl, gvar_i, ary); if (!NIL_P(rb_backref_get())) { while (*s) { sprintf(buf, "$%c", *s++); rb_ary_push(ary, rb_str_new2(buf)); } } return ary; } void rb_alias_variable(name1, name2) ID name1; ID name2; { struct global_entry *entry1, *entry2; if (rb_safe_level() >= 4) rb_raise(rb_eSecurityError, "Insecure: can't alias global variable"); entry2 = rb_global_entry(name2); if (!st_lookup(rb_global_tbl, name1, (st_data_t *)&entry1)) { entry1 = ALLOC(struct global_entry); entry1->id = name1; st_add_direct(rb_global_tbl, name1, (st_data_t)entry1); } else if (entry1->var != entry2->var) { struct global_variable *var = entry1->var; if (var->block_trace) { rb_raise(rb_eRuntimeError, "can't alias in tracer"); } var->counter--; if (var->counter == 0) { struct trace_var *trace = var->trace; while (trace) { struct trace_var *next = trace->next; free(trace); trace = next; } free(var); } } else { return; } entry2->var->counter++; entry1->var = entry2->var; } static int special_generic_ivar = 0; static st_table *generic_iv_tbl; st_table* rb_generic_ivar_table(obj) VALUE obj; { st_table *tbl; if (!FL_TEST(obj, FL_EXIVAR)) return 0; if (!generic_iv_tbl) return 0; if (!st_lookup(generic_iv_tbl, obj, (st_data_t *)&tbl)) return 0; return tbl; } static VALUE generic_ivar_get(obj, id, warn) VALUE obj; ID id; int warn; { st_table *tbl; VALUE val; if (generic_iv_tbl) { if (st_lookup(generic_iv_tbl, obj, (st_data_t *)&tbl)) { if (st_lookup(tbl, id, &val)) { return val; } } } if (warn) { rb_warning("instance variable %s not initialized", rb_id2name(id)); } return Qnil; } static void generic_ivar_set(obj, id, val) VALUE obj; ID id; VALUE val; { st_table *tbl; if (rb_special_const_p(obj)) { special_generic_ivar = 1; } if (!generic_iv_tbl) { generic_iv_tbl = st_init_numtable(); } if (!st_lookup(generic_iv_tbl, obj, (st_data_t *)&tbl)) { FL_SET(obj, FL_EXIVAR); tbl = st_init_numtable(); st_add_direct(generic_iv_tbl, obj, (st_data_t)tbl); st_add_direct(tbl, id, val); return; } st_insert(tbl, id, val); } static VALUE generic_ivar_defined(obj, id) VALUE obj; ID id; { st_table *tbl; VALUE val; if (!generic_iv_tbl) return Qfalse; if (!st_lookup(generic_iv_tbl, obj, (st_data_t *)&tbl)) return Qfalse; if (st_lookup(tbl, id, &val)) { return Qtrue; } return Qfalse; } static int generic_ivar_remove(obj, id, valp) VALUE obj; ID id; VALUE *valp; { st_table *tbl; int status; if (!generic_iv_tbl) return 0; if (!st_lookup(generic_iv_tbl, obj, (st_data_t *)&tbl)) return 0; status = st_delete(tbl, &id, valp); if (tbl->num_entries == 0) { st_delete(generic_iv_tbl, &obj, (st_data_t *)&tbl); st_free_table(tbl); } return status; } void rb_mark_generic_ivar(obj) VALUE obj; { st_table *tbl; if (!generic_iv_tbl) return; if (st_lookup(generic_iv_tbl, obj, (st_data_t *)&tbl)) { rb_mark_tbl(tbl); } } static int givar_mark_i(key, value) ID key; VALUE value; { rb_gc_mark(value); return ST_CONTINUE; } static int givar_i(obj, tbl) VALUE obj; st_table *tbl; { if (rb_special_const_p(obj)) { st_foreach(tbl, givar_mark_i, 0); } return ST_CONTINUE; } void rb_mark_generic_ivar_tbl() { if (!generic_iv_tbl) return; if (special_generic_ivar == 0) return; st_foreach_safe(generic_iv_tbl, givar_i, 0); } void rb_free_generic_ivar(obj) VALUE obj; { st_table *tbl; if (st_delete(generic_iv_tbl, &obj, (st_data_t *)&tbl)) st_free_table(tbl); } void rb_copy_generic_ivar(clone, obj) VALUE clone, obj; { st_table *tbl; if (!generic_iv_tbl) return; if (st_lookup(generic_iv_tbl, obj, (st_data_t *)&tbl)) { st_table *old; if (st_lookup(generic_iv_tbl, clone, (st_data_t *)&old)) { st_free_table(old); st_insert(generic_iv_tbl, clone, (st_data_t)st_copy(tbl)); } else { st_add_direct(generic_iv_tbl, clone, (st_data_t)st_copy(tbl)); } } } static VALUE ivar_get(obj, id, warn) VALUE obj; ID id; int warn; { VALUE val; switch (TYPE(obj)) { case T_OBJECT: case T_CLASS: case T_MODULE: if (ROBJECT(obj)->iv_tbl && st_lookup(ROBJECT(obj)->iv_tbl, id, &val)) return val; break; default: if (FL_TEST(obj, FL_EXIVAR) || rb_special_const_p(obj)) return generic_ivar_get(obj, id, warn); break; } if (warn) { rb_warning("instance variable %s not initialized", rb_id2name(id)); } return Qnil; } VALUE rb_ivar_get(obj, id) VALUE obj; ID id; { return ivar_get(obj, id, Qtrue); } VALUE rb_attr_get(obj, id) VALUE obj; ID id; { return ivar_get(obj, id, Qfalse); } VALUE rb_ivar_set(obj, id, val) VALUE obj; ID id; VALUE val; { if (!OBJ_TAINTED(obj) && rb_safe_level() >= 4) rb_raise(rb_eSecurityError, "Insecure: can't modify instance variable"); if (OBJ_FROZEN(obj)) rb_error_frozen("object"); switch (TYPE(obj)) { case T_OBJECT: case T_CLASS: case T_MODULE: if (!ROBJECT(obj)->iv_tbl) ROBJECT(obj)->iv_tbl = st_init_numtable(); st_insert(ROBJECT(obj)->iv_tbl, id, val); break; default: generic_ivar_set(obj, id, val); break; } return val; } VALUE rb_ivar_defined(obj, id) VALUE obj; ID id; { switch (TYPE(obj)) { case T_OBJECT: case T_CLASS: case T_MODULE: if (ROBJECT(obj)->iv_tbl && st_lookup(ROBJECT(obj)->iv_tbl, id, 0)) return Qtrue; break; default: if (FL_TEST(obj, FL_EXIVAR) || rb_special_const_p(obj)) return generic_ivar_defined(obj, id); break; } return Qfalse; } static int ivar_i(key, entry, ary) ID key; struct global_entry *entry; VALUE ary; { if (rb_is_instance_id(key)) { rb_ary_push(ary, rb_str_new2(rb_id2name(key))); } return ST_CONTINUE; } /* * call-seq: * obj.instance_variables => array * * Returns an array of instance variable names for the receiver. Note * that simply defining an accessor does not create the corresponding * instance variable. * * class Fred * attr_accessor :a1 * def initialize * @iv = 3 * end * end * Fred.new.instance_variables #=> ["@iv"] */ VALUE rb_obj_instance_variables(obj) VALUE obj; { VALUE ary; ary = rb_ary_new(); switch (TYPE(obj)) { case T_OBJECT: case T_CLASS: case T_MODULE: if (ROBJECT(obj)->iv_tbl) { st_foreach_safe(ROBJECT(obj)->iv_tbl, ivar_i, ary); } break; default: if (!generic_iv_tbl) break; if (FL_TEST(obj, FL_EXIVAR) || rb_special_const_p(obj)) { st_table *tbl; if (st_lookup(generic_iv_tbl, obj, (st_data_t *)&tbl)) { st_foreach_safe(tbl, ivar_i, ary); } } break; } return ary; } /* * call-seq: * obj.remove_instance_variable(symbol) => obj * * Removes the named instance variable from obj, returning that * variable's value. * * class Dummy * attr_reader :var * def initialize * @var = 99 * end * def remove * remove_instance_variable(:@var) * end * end * d = Dummy.new * d.var #=> 99 * d.remove #=> 99 * d.var #=> nil */ VALUE rb_obj_remove_instance_variable(obj, name) VALUE obj, name; { VALUE val = Qnil; ID id = rb_to_id(name); if (!OBJ_TAINTED(obj) && rb_safe_level() >= 4) rb_raise(rb_eSecurityError, "Insecure: can't modify instance variable"); if (OBJ_FROZEN(obj)) rb_error_frozen("object"); if (!rb_is_instance_id(id)) { rb_name_error(id, "`%s' is not allowed as an instance variable name", rb_id2name(id)); } switch (TYPE(obj)) { case T_OBJECT: case T_CLASS: case T_MODULE: if (ROBJECT(obj)->iv_tbl && st_delete(ROBJECT(obj)->iv_tbl, (st_data_t*)&id, &val)) { return val; } break; default: if (FL_TEST(obj, FL_EXIVAR) || rb_special_const_p(obj)) { if (generic_ivar_remove(obj, id, &val)) { return val; } } break; } rb_name_error(id, "instance variable %s not defined", rb_id2name(id)); return Qnil; /* not reached */ } NORETURN(static void uninitialized_constant _((VALUE, ID))); static void uninitialized_constant(klass, id) VALUE klass; ID id; { if (klass && klass != rb_cObject) rb_name_error(id, "uninitialized constant %s::%s", rb_class2name(klass), rb_id2name(id)); else { rb_name_error(id, "uninitialized constant %s", rb_id2name(id)); } } static VALUE const_missing(klass, id) VALUE klass; ID id; { return rb_funcall(klass, rb_intern("const_missing"), 1, ID2SYM(id)); } /* * call-seq: * mod.const_missing(sym) => obj * * Invoked when a reference is made to an undefined constant in * mod. It is passed a symbol for the undefined constant, and * returns a value to be used for that constant. The * following code is a (very bad) example: if reference is made to * an undefined constant, it attempts to load a file whose name is * the lowercase version of the constant (thus class Fred is * assumed to be in file fred.rb). If found, it returns the * value of the loaded class. It therefore implements a perverse * kind of autoload facility. * * def Object.const_missing(name) * @looked_for ||= {} * str_name = name.to_s * raise "Class not found: #{name}" if @looked_for[str_name] * @looked_for[str_name] = 1 * file = str_name.downcase * require file * klass = const_get(name) * return klass if klass * raise "Class not found: #{name}" * end * */ VALUE rb_mod_const_missing(klass, name) VALUE klass, name; { ruby_frame = ruby_frame->prev; /* pop frame for "const_missing" */ uninitialized_constant(klass, rb_to_id(name)); return Qnil; /* not reached */ } static struct st_table * check_autoload_table(av) VALUE av; { Check_Type(av, T_DATA); if (RDATA(av)->dmark != (RUBY_DATA_FUNC)rb_mark_tbl || RDATA(av)->dfree != (RUBY_DATA_FUNC)st_free_table) { rb_raise(rb_eTypeError, "wrong autoload table: %s", RSTRING(rb_inspect(av))->ptr); } return (struct st_table *)DATA_PTR(av); } void rb_autoload(mod, id, file) VALUE mod; ID id; const char *file; { VALUE av, fn; struct st_table *tbl; if (!rb_is_const_id(id)) { rb_raise(rb_eNameError, "autoload must be constant name", rb_id2name(id)); } if (!file || !*file) { rb_raise(rb_eArgError, "empty file name"); } if ((tbl = RCLASS(mod)->iv_tbl) && st_lookup(tbl, id, &av) && av != Qundef) return; rb_const_set(mod, id, Qundef); tbl = RCLASS(mod)->iv_tbl; if (st_lookup(tbl, autoload, &av)) { tbl = check_autoload_table(av); } else { av = Data_Wrap_Struct(0 , rb_mark_tbl, st_free_table, 0); st_add_direct(tbl, autoload, av); DATA_PTR(av) = tbl = st_init_numtable(); } fn = rb_str_new2(file); FL_UNSET(fn, FL_TAINT); OBJ_FREEZE(fn); st_insert(tbl, id, (st_data_t)rb_node_newnode(NODE_MEMO, fn, ruby_safe_level, 0)); } static NODE* autoload_delete(mod, id) VALUE mod; ID id; { VALUE val; st_data_t load = 0; st_delete(RCLASS(mod)->iv_tbl, (st_data_t*)&id, 0); if (st_lookup(RCLASS(mod)->iv_tbl, autoload, &val)) { struct st_table *tbl = check_autoload_table(val); st_delete(tbl, (st_data_t*)&id, &load); if (tbl->num_entries == 0) { id = autoload; st_delete(RCLASS(mod)->iv_tbl, (st_data_t*)&id, &val); } } return (NODE *)load; } VALUE rb_autoload_load(klass, id) VALUE klass; ID id; { VALUE file; NODE *load = autoload_delete(klass, id); if (!load || !(file = load->nd_lit) || rb_provided(RSTRING(file)->ptr)) { return Qfalse; } return rb_require_safe(file, load->nd_nth); } static VALUE autoload_file(mod, id) VALUE mod; ID id; { VALUE val, file; struct st_table *tbl; st_data_t load; if (!st_lookup(RCLASS(mod)->iv_tbl, autoload, &val) || !(tbl = check_autoload_table(val)) || !st_lookup(tbl, id, &load)) { return Qnil; } file = ((NODE *)load)->nd_lit; Check_Type(file, T_STRING); if (!RSTRING(file)->ptr || !*RSTRING(file)->ptr) { rb_raise(rb_eArgError, "empty file name"); } if (!rb_provided(RSTRING(file)->ptr)) { return file; } /* already loaded but not defined */ st_delete(tbl, (st_data_t*)&id, 0); if (!tbl->num_entries) { id = autoload; st_delete(RCLASS(mod)->iv_tbl, (st_data_t*)&id, &val); } return Qnil; } VALUE rb_autoload_p(mod, id) VALUE mod; ID id; { struct st_table *tbl = RCLASS(mod)->iv_tbl; VALUE val; if (!tbl || !st_lookup(tbl, id, &val) || val != Qundef) { return Qnil; } return autoload_file(mod, id); } static VALUE rb_const_get_0(klass, id, exclude, recurse) VALUE klass; ID id; int exclude, recurse; { VALUE value, tmp; int mod_retry = 0; tmp = klass; retry: while (tmp) { while (RCLASS(tmp)->iv_tbl && st_lookup(RCLASS(tmp)->iv_tbl,id,&value)) { if (value == Qundef) { if (!RTEST(rb_autoload_load(tmp, id))) break; continue; } if (exclude && tmp == rb_cObject && klass != rb_cObject) { rb_warn("toplevel constant %s referenced by %s::%s", rb_id2name(id), rb_class2name(klass), rb_id2name(id)); } return value; } if (!recurse && klass != rb_cObject) break; tmp = RCLASS(tmp)->super; } if (!exclude && !mod_retry && BUILTIN_TYPE(klass) == T_MODULE) { mod_retry = 1; tmp = rb_cObject; goto retry; } return const_missing(klass, id); } VALUE rb_const_get_from(klass, id) VALUE klass; ID id; { return rb_const_get_0(klass, id, Qtrue, Qtrue); } VALUE rb_const_get(klass, id) VALUE klass; ID id; { return rb_const_get_0(klass, id, Qfalse, Qtrue); } VALUE rb_const_get_at(klass, id) VALUE klass; ID id; { return rb_const_get_0(klass, id, Qtrue, Qfalse); } /* * call-seq: * remove_const(sym) => obj * * Removes the definition of the given constant, returning that * constant's value. Predefined classes and singleton objects (such as * true) cannot be removed. */ VALUE rb_mod_remove_const(mod, name) VALUE mod, name; { ID id = rb_to_id(name); VALUE val; if (!rb_is_const_id(id)) { rb_name_error(id, "`%s' is not allowed as a constant name", rb_id2name(id)); } if (!OBJ_TAINTED(mod) && rb_safe_level() >= 4) rb_raise(rb_eSecurityError, "Insecure: can't remove constant"); if (OBJ_FROZEN(mod)) rb_error_frozen("class/module"); if (RCLASS(mod)->iv_tbl && st_delete(ROBJECT(mod)->iv_tbl, (st_data_t*)&id, &val)) { if (val == Qundef) { autoload_delete(mod, id); val = Qnil; } return val; } if (rb_const_defined_at(mod, id)) { rb_name_error(id, "cannot remove %s::%s", rb_class2name(mod), rb_id2name(id)); } rb_name_error(id, "constant %s::%s not defined", rb_class2name(mod), rb_id2name(id)); return Qnil; /* not reached */ } static int sv_i(key, value, tbl) ID key; VALUE value; st_table *tbl; { if (rb_is_const_id(key)) { if (!st_lookup(tbl, key, 0)) { st_insert(tbl, key, key); } } return ST_CONTINUE; } void* rb_mod_const_at(mod, data) VALUE mod; void *data; { st_table *tbl = data; if (!tbl) { tbl = st_init_numtable(); } if (RCLASS(mod)->iv_tbl) { st_foreach_safe(RCLASS(mod)->iv_tbl, sv_i, (st_data_t)tbl); } return tbl; } void* rb_mod_const_of(mod, data) VALUE mod; void *data; { VALUE tmp = mod; for (;;) { data = rb_mod_const_at(tmp, data); tmp = RCLASS(tmp)->super; if (!tmp) break; if (tmp == rb_cObject && mod != rb_cObject) break; } return data; } static int list_i(key, value, ary) ID key, value; VALUE ary; { rb_ary_push(ary, rb_str_new2(rb_id2name(key))); return ST_CONTINUE; } VALUE rb_const_list(data) void *data; { st_table *tbl = data; VALUE ary; if (!tbl) return rb_ary_new2(0); ary = rb_ary_new2(tbl->num_entries); st_foreach(tbl, list_i, ary); st_free_table(tbl); return ary; } /* * call-seq: * mod.constants => array * * Returns an array of the names of the constants accessible in * mod. This includes the names of constants in any included * modules (example at start of section). */ VALUE rb_mod_constants(mod) VALUE mod; { return rb_const_list(rb_mod_const_of(mod, 0)); } static int rb_const_defined_0(klass, id, exclude, recurse) VALUE klass; ID id; int exclude, recurse; { VALUE value, tmp; int mod_retry = 0; tmp = klass; retry: while (tmp) { if (RCLASS(tmp)->iv_tbl && st_lookup(RCLASS(tmp)->iv_tbl, id, &value)) { if (value == Qundef && NIL_P(autoload_file(klass, id))) return Qfalse; return Qtrue; } if (!recurse && klass != rb_cObject) break; tmp = RCLASS(tmp)->super; } if (!exclude && !mod_retry && BUILTIN_TYPE(klass) == T_MODULE) { mod_retry = 1; tmp = rb_cObject; goto retry; } return Qfalse; } int rb_const_defined_from(klass, id) VALUE klass; ID id; { return rb_const_defined_0(klass, id, Qtrue, Qtrue); } int rb_const_defined(klass, id) VALUE klass; ID id; { return rb_const_defined_0(klass, id, Qfalse, Qtrue); } int rb_const_defined_at(klass, id) VALUE klass; ID id; { return rb_const_defined_0(klass, id, Qtrue, Qfalse); } static void mod_av_set(klass, id, val, isconst) VALUE klass; ID id; VALUE val; int isconst; { char *dest = isconst ? "constant" : "class variable"; if (!OBJ_TAINTED(klass) && rb_safe_level() >= 4) rb_raise(rb_eSecurityError, "Insecure: can't set %s", dest); if (OBJ_FROZEN(klass)) { if (BUILTIN_TYPE(klass) == T_MODULE) { rb_error_frozen("module"); } else { rb_error_frozen("class"); } } if (!RCLASS(klass)->iv_tbl) { RCLASS(klass)->iv_tbl = st_init_numtable(); } else if (isconst) { VALUE value = Qfalse; if (st_lookup(RCLASS(klass)->iv_tbl, id, &value)) { if (value == Qundef) autoload_delete(klass, id); else rb_warn("already initialized %s %s", dest, rb_id2name(id)); } } st_insert(RCLASS(klass)->iv_tbl, id, val); } void rb_const_set(klass, id, val) VALUE klass; ID id; VALUE val; { if (NIL_P(klass)) { rb_raise(rb_eTypeError, "no class/module to define constant %s", rb_id2name(id)); } mod_av_set(klass, id, val, Qtrue); } void rb_define_const(klass, name, val) VALUE klass; const char *name; VALUE val; { ID id = rb_intern(name); if (!rb_is_const_id(id)) { rb_warn("rb_define_const: invalid name `%s' for constant", name); } if (klass == rb_cObject) { rb_secure(4); } rb_const_set(klass, id, val); } void rb_define_global_const(name, val) const char *name; VALUE val; { rb_define_const(rb_cObject, name, val); } static VALUE original_module(c) VALUE c; { if (TYPE(c) == T_ICLASS) return RBASIC(c)->klass; return c; } static void cvar_override_check(id, a) ID id; VALUE a; { VALUE base = original_module(a); a = RCLASS(a)->super; while (a) { if (RCLASS(a)->iv_tbl) { if (st_lookup(RCLASS(a)->iv_tbl,id,0)) { rb_warning("class variable %s of %s is overridden by %s", rb_id2name(id), rb_class2name(original_module(a)), rb_class2name(base)); } } a = RCLASS(a)->super; } } void rb_cvar_set(klass, id, val, warn) VALUE klass; ID id; VALUE val; int warn; { VALUE tmp; tmp = klass; while (tmp) { if (RCLASS(tmp)->iv_tbl && st_lookup(RCLASS(tmp)->iv_tbl,id,0)) { if (OBJ_FROZEN(tmp)) rb_error_frozen("class/module"); if (!OBJ_TAINTED(tmp) && rb_safe_level() >= 4) rb_raise(rb_eSecurityError, "Insecure: can't modify class variable"); if (warn && RTEST(ruby_verbose) && klass != tmp) { rb_warning("already initialized class variable %s", rb_id2name(id)); } st_insert(RCLASS(tmp)->iv_tbl,id,val); if (RTEST(ruby_verbose)) { cvar_override_check(id, tmp); } return; } tmp = RCLASS(tmp)->super; } mod_av_set(klass, id, val, Qfalse); } VALUE rb_cvar_get(klass, id) VALUE klass; ID id; { VALUE value; VALUE tmp; tmp = klass; while (tmp) { if (RCLASS(tmp)->iv_tbl) { if (st_lookup(RCLASS(tmp)->iv_tbl,id,&value)) { if (RTEST(ruby_verbose)) { cvar_override_check(id, tmp); } return value; } } tmp = RCLASS(tmp)->super; } rb_name_error(id,"uninitialized class variable %s in %s", rb_id2name(id), rb_class2name(klass)); return Qnil; /* not reached */ } VALUE rb_cvar_defined(klass, id) VALUE klass; ID id; { VALUE tmp; tmp = klass; while (tmp) { if (RCLASS(tmp)->iv_tbl && st_lookup(RCLASS(tmp)->iv_tbl,id,0)) { return Qtrue; } tmp = RCLASS(tmp)->super; } return Qfalse; } void rb_cv_set(klass, name, val) VALUE klass; const char *name; VALUE val; { ID id = rb_intern(name); if (!rb_is_class_id(id)) { rb_name_error(id, "wrong class variable name %s", name); } rb_cvar_set(klass, id, val, Qfalse); } VALUE rb_cv_get(klass, name) VALUE klass; const char *name; { ID id = rb_intern(name); if (!rb_is_class_id(id)) { rb_name_error(id, "wrong class variable name %s", name); } return rb_cvar_get(klass, id); } void rb_define_class_variable(klass, name, val) VALUE klass; const char *name; VALUE val; { ID id = rb_intern(name); if (!rb_is_class_id(id)) { rb_name_error(id, "wrong class variable name %s", name); } rb_cvar_set(klass, id, val, Qtrue); } static int cv_i(key, value, ary) ID key; VALUE value; VALUE ary; { if (rb_is_class_id(key)) { VALUE kval = rb_str_new2(rb_id2name(key)); if (!rb_ary_includes(ary, kval)) { rb_ary_push(ary, kval); } } return ST_CONTINUE; } /* * call-seq: * mod.class_variables => array * * Returns an array of the names of class variables in mod and * the ancestors of mod. * * class One * @@var1 = 1 * end * class Two < One * @@var2 = 2 * end * One.class_variables #=> ["@@var1"] * Two.class_variables #=> ["@@var2", "@@var1"] */ VALUE rb_mod_class_variables(obj) VALUE obj; { VALUE ary = rb_ary_new(); for (;;) { if (RCLASS(obj)->iv_tbl) { st_foreach_safe(RCLASS(obj)->iv_tbl, cv_i, ary); } obj = RCLASS(obj)->super; if (!obj) break; } return ary; } /* * call-seq: * remove_class_variable(sym) => obj * * Removes the definition of the sym, returning that * constant's value. * * class Dummy * @@var = 99 * puts @@var * remove_class_variable(:@@var) * puts(defined? @@var) * end * * produces: * * 99 * nil */ VALUE rb_mod_remove_cvar(mod, name) VALUE mod, name; { ID id = rb_to_id(name); VALUE val; if (!rb_is_class_id(id)) { rb_name_error(id, "wrong class variable name %s", rb_id2name(id)); } if (!OBJ_TAINTED(mod) && rb_safe_level() >= 4) rb_raise(rb_eSecurityError, "Insecure: can't remove class variable"); if (OBJ_FROZEN(mod)) rb_error_frozen("class/module"); if (RCLASS(mod)->iv_tbl && st_delete(ROBJECT(mod)->iv_tbl, (st_data_t*)&id, &val)) { return val; } if (rb_cvar_defined(mod, id)) { rb_name_error(id, "cannot remove %s for %s", rb_id2name(id), rb_class2name(mod)); } rb_name_error(id, "class variable %s not defined for %s", rb_id2name(id), rb_class2name(mod)); return Qnil; /* not reached */ } VALUE rb_iv_get(obj, name) VALUE obj; const char *name; { ID id = rb_intern(name); return rb_ivar_get(obj, id); } VALUE rb_iv_set(obj, name, val) VALUE obj; const char *name; VALUE val; { ID id = rb_intern(name); return rb_ivar_set(obj, id, val); } /********************************************************************** version.c - $Author: shyouhei $ $Date: 2007-02-13 00:01:19 +0100 (Tue, 13 Feb 2007) $ created at: Thu Sep 30 20:08:01 JST 1993 Copyright (C) 1993-2003 Yukihiro Matsumoto **********************************************************************/ #include "ruby.h" #include "version.h" #include const char ruby_version[] = RUBY_VERSION; const char ruby_release_date[] = RUBY_RELEASE_DATE; const char ruby_platform[] = RUBY_PLATFORM; const int ruby_patchlevel = RUBY_PATCHLEVEL; void Init_version() { VALUE v = rb_obj_freeze(rb_str_new2(ruby_version)); VALUE d = rb_obj_freeze(rb_str_new2(ruby_release_date)); VALUE p = rb_obj_freeze(rb_str_new2(ruby_platform)); rb_define_global_const("RUBY_VERSION", v); rb_define_global_const("RUBY_RELEASE_DATE", d); rb_define_global_const("RUBY_PLATFORM", p); rb_define_global_const("RUBY_PATCHLEVEL", INT2FIX(RUBY_PATCHLEVEL)); /* obsolete constants */ rb_define_global_const("VERSION", v); rb_define_global_const("RELEASE_DATE", d); rb_define_global_const("PLATFORM", p); } void ruby_show_version() { printf("ruby %s (%s patchlevel %d) [%s]\n", RUBY_VERSION, RUBY_RELEASE_DATE, RUBY_PATCHLEVEL, RUBY_PLATFORM); fflush(stdout); } void ruby_show_copyright() { printf("ruby - Copyright (C) 1993-%d Yukihiro Matsumoto\n", RUBY_RELEASE_YEAR); exit(0); } #define PACKAGE_NAME "" #define PACKAGE_TARNAME "" #define PACKAGE_VERSION "" #define PACKAGE_STRING "" #define PACKAGE_BUGREPORT "" #define USE_BUILTIN_FRAME_ADDRESS 1 #define STDC_HEADERS 1 #define HAVE_SYS_TYPES_H 1 #define HAVE_SYS_STAT_H 1 #define HAVE_STDLIB_H 1 #define HAVE_STRING_H 1 #define HAVE_MEMORY_H 1 #define HAVE_STRINGS_H 1 #define HAVE_INTTYPES_H 1 #define HAVE_STDINT_H 1 #define HAVE_UNISTD_H 1 #define HAVE_LONG_LONG 1 #define HAVE_OFF_T 1 #define SIZEOF_INT 4 #define SIZEOF_SHORT 2 #define SIZEOF_LONG 4 #define SIZEOF_LONG_LONG 8 #define SIZEOF___INT64 0 #define SIZEOF_OFF_T 8 #define SIZEOF_VOIDP 4 #define SIZEOF_FLOAT 4 #define SIZEOF_DOUBLE 8 #define SIZEOF_TIME_T 4 #define rb_pid_t pid_t #define rb_gid_t gid_t #define rb_uid_t uid_t #define HAVE_PROTOTYPES 1 #define TOKEN_PASTE(x,y) x##y #define HAVE_STDARG_PROTOTYPES 1 #define NORETURN(x) __attribute__ ((noreturn)) x #define NOINLINE(x) __attribute__ ((noinline)) x #define HAVE_DECL_SYS_NERR 1 #define HAVE_LIBDL 1 #define HAVE_DIRENT_H 1 #define STDC_HEADERS 1 #define HAVE_SYS_WAIT_H 1 #define HAVE_STDLIB_H 1 #define HAVE_STRING_H 1 #define HAVE_UNISTD_H 1 #define HAVE_LIMITS_H 1 #define HAVE_SYS_FILE_H 1 #define HAVE_SYS_IOCTL_H 1 #define HAVE_SYS_SYSCALL_H 1 #define HAVE_FCNTL_H 1 #define HAVE_SYS_FCNTL_H 1 #define HAVE_SYS_SELECT_H 1 #define HAVE_SYS_TIME_H 1 #define HAVE_SYS_TIMES_H 1 #define HAVE_SYS_PARAM_H 1 #define HAVE_PWD_H 1 #define HAVE_GRP_H 1 #define HAVE_UTIME_H 1 #define HAVE_MEMORY_H 1 #define HAVE_SYS_RESOURCE_H 1 #define HAVE_NETINET_IN_SYSTM_H 1 #define HAVE_FLOAT_H 1 #define HAVE_PTHREAD_H 1 #define HAVE_UCONTEXT_H 1 #define SIZEOF_RLIM_T 8 #define HAVE_STRUCT_STAT_ST_BLKSIZE 1 #define HAVE_ST_BLKSIZE 1 #define HAVE_STRUCT_STAT_ST_BLOCKS 1 #define HAVE_ST_BLOCKS 1 #define HAVE_STRUCT_STAT_ST_RDEV 1 #define HAVE_ST_RDEV 1 #define GETGROUPS_T gid_t #define RETSIGTYPE void #define HAVE_ALLOCA_H 1 #define HAVE_ALLOCA 1 #define HAVE_FSEEKO 1 #define HAVE_FTELLO 1 #define HAVE_DUP2 1 #define HAVE_MEMMOVE 1 #define HAVE_STRCASECMP 1 #define HAVE_STRNCASECMP 1 #define HAVE_STRERROR 1 #define HAVE_STRFTIME 1 #define HAVE_STRCHR 1 #define HAVE_STRSTR 1 #define HAVE_STRTOUL 1 #define HAVE_CRYPT 1 #define HAVE_FLOCK 1 #define HAVE_VSNPRINTF 1 #define HAVE_ISNAN 1 #define HAVE_FINITE 1 #define HAVE_ISINF 1 #define HAVE_HYPOT 1 #define HAVE_ACOSH 1 #define HAVE_ERF 1 #define HAVE_FMOD 1 #define HAVE_KILLPG 1 #define HAVE_WAIT4 1 #define HAVE_WAITPID 1 #define HAVE_SYSCALL 1 #define HAVE_CHROOT 1 #define HAVE_FSYNC 1 #define HAVE_GETCWD 1 #define HAVE_TRUNCATE 1 #define HAVE_FTRUNCATE 1 #define HAVE_TIMES 1 #define HAVE_UTIMES 1 #define HAVE_FCNTL 1 #define HAVE_LOCKF 1 #define HAVE_LSTAT 1 #define HAVE_SYMLINK 1 #define HAVE_LINK 1 #define HAVE_READLINK 1 #define HAVE_SETITIMER 1 #define HAVE_SETRUID 1 #define HAVE_SETEUID 1 #define HAVE_SETREUID 1 #define HAVE_SETRGID 1 #define HAVE_SETEGID 1 #define HAVE_SETREGID 1 #define HAVE_ISSETUGID 1 #define HAVE_PAUSE 1 #define HAVE_LCHOWN 1 #define HAVE_LCHMOD 1 #define HAVE_GETPGRP 1 #define HAVE_SETPGRP 1 #define HAVE_GETPGID 1 #define HAVE_SETPGID 1 #define HAVE_INITGROUPS 1 #define HAVE_GETGROUPS 1 #define HAVE_SETGROUPS 1 #define HAVE_GETPRIORITY 1 #define HAVE_GETRLIMIT 1 #define HAVE_SETRLIMIT 1 #define HAVE_SYSCONF 1 #define HAVE_DLOPEN 1 #define HAVE_SIGPROCMASK 1 #define HAVE_SIGACTION 1 #define HAVE__SETJMP 1 #define HAVE_SETSID 1 #define HAVE_TELLDIR 1 #define HAVE_SEEKDIR 1 #define HAVE_FCHMOD 1 #define HAVE_MKTIME 1 #define HAVE_TIMEGM 1 #define HAVE_GETTIMEOFDAY 1 #define HAVE_COSH 1 #define HAVE_SINH 1 #define HAVE_TANH 1 #define HAVE_ROUND 1 #define HAVE_SETUID 1 #define HAVE_SETGID 1 #define HAVE_SETENV 1 #define HAVE_UNSETENV 1 #define HAVE_STRUCT_TM_TM_ZONE 1 #define HAVE_TM_ZONE 1 #define HAVE_STRUCT_TM_TM_GMTOFF 1 #define HAVE_DAYLIGHT 1 #define HAVE_VAR_TIMEZONE 1 #define TYPEOF_VAR_TIMEZONE long #define NEGATIVE_TIME_T 1 #define POSIX_SIGNAL 1 #define GETPGRP_VOID 1 #define SETPGRP_VOID 1 #define RSHIFT(x,y) ((x)>>(int)y) #define FILE_COUNT _r #define FILE_READPTR _p #define NEED_IO_SEEK_BETWEEN_RW 1 #define HAVE__SC_CLK_TCK 1 #define STACK_GROW_DIRECTION -1 #define DEFAULT_KCODE KCODE_NONE #define DLEXT_MAXLEN 7 #define DLEXT ".bundle" #define RUBY_LIB "/usr/local/lib/ruby/1.8" #define RUBY_SITE_LIB "/usr/local/lib/ruby/site_ruby" #define RUBY_SITE_LIB2 "/usr/local/lib/ruby/site_ruby/1.8" #define RUBY_PLATFORM "i686-darwin9.8.0" #define RUBY_ARCHLIB "/usr/local/lib/ruby/1.8/i686-darwin9.8.0" #define RUBY_SITE_ARCHLIB "/usr/local/lib/ruby/site_ruby/1.8/i686-darwin9.8.0" /************************************************ defines.h - $Author: shyouhei $ $Date: 2008-06-15 16:06:16 +0200 (Sun, 15 Jun 2008) $ created at: Wed May 18 00:21:44 JST 1994 ************************************************/ #ifndef DEFINES_H #define DEFINES_H #define RUBY #ifdef __cplusplus # ifndef HAVE_PROTOTYPES # define HAVE_PROTOTYPES 1 # endif # ifndef HAVE_STDARG_PROTOTYPES # define HAVE_STDARG_PROTOTYPES 1 # endif #endif #undef _ #ifdef HAVE_PROTOTYPES # define _(args) args #else # define _(args) () #endif #undef __ #ifdef HAVE_STDARG_PROTOTYPES # define __(args) args #else # define __(args) () #endif #ifdef __cplusplus #define ANYARGS ... #else #define ANYARGS #endif #define xmalloc ruby_xmalloc #define xcalloc ruby_xcalloc #define xrealloc ruby_xrealloc #define xfree ruby_xfree void *xmalloc _((long)); void *xcalloc _((long,long)); void *xrealloc _((void*,long)); void xfree _((void*)); #if SIZEOF_LONG_LONG > 0 # define LONG_LONG long long #elif SIZEOF___INT64 > 0 # define HAVE_LONG_LONG 1 # define LONG_LONG __int64 # undef SIZEOF_LONG_LONG # define SIZEOF_LONG_LONG SIZEOF___INT64 #endif #if SIZEOF_INT*2 <= SIZEOF_LONG_LONG # define BDIGIT unsigned int # define SIZEOF_BDIGITS SIZEOF_INT # define BDIGIT_DBL unsigned LONG_LONG # define BDIGIT_DBL_SIGNED LONG_LONG #elif SIZEOF_INT*2 <= SIZEOF_LONG # define BDIGIT unsigned int # define SIZEOF_BDIGITS SIZEOF_INT # define BDIGIT_DBL unsigned long # define BDIGIT_DBL_SIGNED long #elif SIZEOF_SHORT*2 <= SIZEOF_LONG # define BDIGIT unsigned short # define SIZEOF_BDIGITS SIZEOF_SHORT # define BDIGIT_DBL unsigned long # define BDIGIT_DBL_SIGNED long #else # define BDIGIT unsigned short # define SIZEOF_BDIGITS (SIZEOF_LONG/2) # define BDIGIT_DBL unsigned long # define BDIGIT_DBL_SIGNED long #endif #ifdef __CYGWIN__ #undef _WIN32 #endif #if defined(MSDOS) || defined(_WIN32) || defined(__human68k__) || defined(__EMX__) #define DOSISH 1 #ifndef _WIN32_WCE # define DOSISH_DRIVE_LETTER #endif #endif /* define RUBY_USE_EUC/SJIS for default kanji-code */ #ifndef DEFAULT_KCODE #if defined(DOSISH) || defined(__CYGWIN__) || defined(__MACOS__) || defined(OS2) #define DEFAULT_KCODE KCODE_SJIS #else #define DEFAULT_KCODE KCODE_EUC #endif #endif #ifdef __NeXT__ /* NextStep, OpenStep, Rhapsody */ #ifndef S_IRUSR #define S_IRUSR 0000400 /* read permission, owner */ #endif #ifndef S_IRGRP #define S_IRGRP 0000040 /* read permission, group */ #endif #ifndef S_IROTH #define S_IROTH 0000004 /* read permission, other */ #endif #ifndef S_IWUSR #define S_IWUSR 0000200 /* write permission, owner */ #endif #ifndef S_IWGRP #define S_IWGRP 0000020 /* write permission, group */ #endif #ifndef S_IWOTH #define S_IWOTH 0000002 /* write permission, other */ #endif #ifndef S_IXUSR #define S_IXUSR 0000100 /* execute/search permission, owner */ #endif #ifndef S_IXGRP #define S_IXGRP 0000010 /* execute/search permission, group */ #endif #ifndef S_IXOTH #define S_IXOTH 0000001 /* execute/search permission, other */ #endif #ifndef S_IRWXU #define S_IRWXU 0000700 /* read, write, execute permissions, owner */ #endif #ifndef S_IRWXG #define S_IRWXG 0000070 /* read, write, execute permissions, group */ #endif #ifndef S_IRWXO #define S_IRWXO 0000007 /* read, write, execute permissions, other */ #endif #ifndef S_ISBLK #define S_ISBLK(mode) (((mode) & (0170000)) == (0060000)) #endif #ifndef S_ISCHR #define S_ISCHR(mode) (((mode) & (0170000)) == (0020000)) #endif #ifndef S_ISDIR #define S_ISDIR(mode) (((mode) & (0170000)) == (0040000)) #endif #ifndef S_ISFIFO #define S_ISFIFO(mode) (((mode) & (0170000)) == (0010000)) #endif #ifndef S_ISREG #define S_ISREG(mode) (((mode) & (0170000)) == (0100000)) #endif /* Do not trust WORDS_BIGENDIAN from configure since -arch compiler flag may result in a different endian. Instead trust __BIG_ENDIAN__ and __LITTLE_ENDIAN__ which are set correctly by -arch. */ #undef WORDS_BIGENDIAN #ifdef __BIG_ENDIAN__ #define WORDS_BIGENDIAN #endif #ifndef __APPLE__ /* NextStep, OpenStep (but not Rhapsody) */ #ifndef GETPGRP_VOID #define GETPGRP_VOID 1 #endif #ifndef WNOHANG #define WNOHANG 01 #endif #ifndef WUNTRACED #define WUNTRACED 02 #endif #ifndef X_OK #define X_OK 1 #endif #endif /* __APPLE__ */ #endif /* NeXT */ #ifdef _WIN32 #include "win32/win32.h" #endif #if defined(__VMS) #include "vms.h" #endif #if defined(__BEOS__) #include /* intern.h needs fd_set definition */ #endif #ifdef RUBY_EXPORT #undef RUBY_EXTERN #endif #ifndef RUBY_EXTERN #define RUBY_EXTERN extern #endif #ifndef EXTERN #define EXTERN RUBY_EXTERN /* deprecated */ #endif #ifndef RUBY_MBCHAR_MAXSIZE #define RUBY_MBCHAR_MAXSIZE INT_MAX /* MB_CUR_MAX will not work well in C locale */ #endif #if defined(sparc) || defined(__sparc__) static inline void flush_register_windows(void) { asm #ifdef __GNUC__ volatile #endif # if defined(__sparc_v9__) || defined(__sparcv9) || defined(__arch64__) ("flushw") # else ("ta 0x03") # endif /* trap always to flush register windows if we are on a Sparc system */ ; } # define FLUSH_REGISTER_WINDOWS flush_register_windows() #elif defined(__ia64) void *rb_ia64_bsp(void); void rb_ia64_flushrs(void); # define FLUSH_REGISTER_WINDOWS rb_ia64_flushrs() #else # define FLUSH_REGISTER_WINDOWS ((void)0) #endif #if defined(DOSISH) #define PATH_SEP ";" #elif defined(riscos) #define PATH_SEP "," #else #define PATH_SEP ":" #endif #define PATH_SEP_CHAR PATH_SEP[0] #if defined(__human68k__) #define PATH_ENV "path" #else #define PATH_ENV "PATH" #endif #if defined(DOSISH) && !defined(__human68k__) && !defined(__EMX__) #define ENV_IGNORECASE #endif #ifndef CASEFOLD_FILESYSTEM # if defined DOSISH || defined __VMS # define CASEFOLD_FILESYSTEM 1 # else # define CASEFOLD_FILESYSTEM 0 # endif #endif #ifndef DLEXT_MAXLEN #define DLEXT_MAXLEN 4 #endif #ifndef RUBY_PLATFORM #define RUBY_PLATFORM "unknown-unknown" #endif #endif /********************************************************************** dln.h - $Author: shyouhei $ $Date: 2007-02-13 00:01:19 +0100 (Tue, 13 Feb 2007) $ created at: Wed Jan 19 16:53:09 JST 1994 Copyright (C) 1993-2003 Yukihiro Matsumoto **********************************************************************/ #ifndef DLN_H #define DLN_H #ifdef __cplusplus # ifndef HAVE_PROTOTYPES # define HAVE_PROTOTYPES 1 # endif # ifndef HAVE_STDARG_PROTOTYPES # define HAVE_STDARG_PROTOTYPES 1 # endif #endif #undef _ #ifdef HAVE_PROTOTYPES # define _(args) args #else # define _(args) () #endif char *dln_find_exe _((const char*,const char*)); char *dln_find_file _((const char*,const char*)); #ifdef USE_DLN_A_OUT extern char *dln_argv0; #endif void *dln_load _((const char*)); #endif /********************************************************************** env.h - $Author: knu $ $Date: 2007-03-03 08:30:46 +0100 (Sat, 03 Mar 2007) $ created at: Mon Jul 11 11:53:03 JST 1994 Copyright (C) 1993-2003 Yukihiro Matsumoto **********************************************************************/ #ifndef ENV_H #define ENV_H extern struct FRAME { VALUE self; int argc; ID last_func; ID orig_func; VALUE last_class; struct FRAME *prev; struct FRAME *tmp; struct RNode *node; int iter; int flags; unsigned long uniq; } *ruby_frame; void rb_gc_mark_frame _((struct FRAME *)); #define FRAME_DMETH 1 #define FRAME_FUNC 2 extern struct SCOPE { struct RBasic super; ID *local_tbl; VALUE *local_vars; int flags; } *ruby_scope; #define SCOPE_ALLOCA 0 #define SCOPE_MALLOC 1 #define SCOPE_NOSTACK 2 #define SCOPE_DONT_RECYCLE 4 #define SCOPE_CLONE 8 extern int ruby_in_eval; extern VALUE ruby_class; struct RVarmap { struct RBasic super; ID id; VALUE val; struct RVarmap *next; }; extern struct RVarmap *ruby_dyna_vars; #endif /* ENV_H */ /********************************************************************** intern.h - $Author: shyouhei $ $Date: 2009-01-22 07:18:48 +0100 (Thu, 22 Jan 2009) $ created at: Thu Jun 10 14:22:17 JST 1993 Copyright (C) 1993-2003 Yukihiro Matsumoto Copyright (C) 2000 Network Applied Communication Laboratory, Inc. Copyright (C) 2000 Information-technology Promotion Agency, Japan **********************************************************************/ /* * Functions and variables that are used by more than one source file of * the kernel. */ #define ID_ALLOCATOR 1 /* array.c */ void rb_mem_clear _((register VALUE*, register long)); VALUE rb_assoc_new _((VALUE, VALUE)); VALUE rb_check_array_type _((VALUE)); VALUE rb_ary_new _((void)); VALUE rb_ary_new2 _((long)); VALUE rb_ary_new3 __((long,...)); VALUE rb_ary_new4 _((long, const VALUE *)); VALUE rb_ary_freeze _((VALUE)); VALUE rb_ary_aref _((int, VALUE*, VALUE)); void rb_ary_store _((VALUE, long, VALUE)); VALUE rb_ary_dup _((VALUE)); VALUE rb_ary_to_ary _((VALUE)); VALUE rb_ary_to_s _((VALUE)); VALUE rb_ary_push _((VALUE, VALUE)); VALUE rb_ary_pop _((VALUE)); VALUE rb_ary_shift _((VALUE)); VALUE rb_ary_unshift _((VALUE, VALUE)); VALUE rb_ary_entry _((VALUE, long)); VALUE rb_ary_each _((VALUE)); VALUE rb_ary_join _((VALUE, VALUE)); VALUE rb_ary_print_on _((VALUE, VALUE)); VALUE rb_ary_reverse _((VALUE)); VALUE rb_ary_sort _((VALUE)); VALUE rb_ary_sort_bang _((VALUE)); VALUE rb_ary_delete _((VALUE, VALUE)); VALUE rb_ary_delete_at _((VALUE, long)); VALUE rb_ary_clear _((VALUE)); VALUE rb_ary_plus _((VALUE, VALUE)); VALUE rb_ary_concat _((VALUE, VALUE)); VALUE rb_ary_assoc _((VALUE, VALUE)); VALUE rb_ary_rassoc _((VALUE, VALUE)); VALUE rb_ary_includes _((VALUE, VALUE)); VALUE rb_ary_cmp _((VALUE, VALUE)); VALUE rb_protect_inspect _((VALUE(*)(ANYARGS),VALUE,VALUE)); VALUE rb_inspecting_p _((VALUE)); VALUE rb_check_array_value _((VALUE)); VALUE rb_values_at _((VALUE, long, int, VALUE*, VALUE(*) _((VALUE,long)))); /* bignum.c */ VALUE rb_big_clone _((VALUE)); void rb_big_2comp _((VALUE)); VALUE rb_big_norm _((VALUE)); VALUE rb_uint2big _((unsigned long)); VALUE rb_int2big _((long)); VALUE rb_uint2inum _((unsigned long)); VALUE rb_int2inum _((long)); VALUE rb_cstr_to_inum _((const char*, int, int)); VALUE rb_str_to_inum _((VALUE, int, int)); VALUE rb_cstr2inum _((const char*, int)); VALUE rb_str2inum _((VALUE, int)); VALUE rb_big2str _((VALUE, int)); VALUE rb_big2str0 _((VALUE, int, int)); long rb_big2long _((VALUE)); #define rb_big2int(x) rb_big2long(x) unsigned long rb_big2ulong _((VALUE)); #define rb_big2uint(x) rb_big2ulong(x) #if HAVE_LONG_LONG VALUE rb_ll2inum _((LONG_LONG)); VALUE rb_ull2inum _((unsigned LONG_LONG)); LONG_LONG rb_big2ll _((VALUE)); unsigned LONG_LONG rb_big2ull _((VALUE)); #endif /* HAVE_LONG_LONG */ void rb_quad_pack _((char*,VALUE)); VALUE rb_quad_unpack _((const char*,int)); VALUE rb_dbl2big _((double)); double rb_big2dbl _((VALUE)); VALUE rb_big_plus _((VALUE, VALUE)); VALUE rb_big_minus _((VALUE, VALUE)); VALUE rb_big_mul _((VALUE, VALUE)); VALUE rb_big_divmod _((VALUE, VALUE)); VALUE rb_big_pow _((VALUE, VALUE)); VALUE rb_big_and _((VALUE, VALUE)); VALUE rb_big_or _((VALUE, VALUE)); VALUE rb_big_xor _((VALUE, VALUE)); VALUE rb_big_lshift _((VALUE, VALUE)); VALUE rb_big_rand _((VALUE, double*)); /* class.c */ VALUE rb_class_boot _((VALUE)); VALUE rb_class_new _((VALUE)); VALUE rb_mod_init_copy _((VALUE, VALUE)); VALUE rb_class_init_copy _((VALUE, VALUE)); VALUE rb_singleton_class_clone _((VALUE)); void rb_singleton_class_attached _((VALUE,VALUE)); VALUE rb_make_metaclass _((VALUE, VALUE)); void rb_check_inheritable _((VALUE)); VALUE rb_class_inherited _((VALUE, VALUE)); VALUE rb_define_class_id _((ID, VALUE)); VALUE rb_module_new _((void)); VALUE rb_define_module_id _((ID)); VALUE rb_mod_included_modules _((VALUE)); VALUE rb_mod_include_p _((VALUE, VALUE)); VALUE rb_mod_ancestors _((VALUE)); VALUE rb_class_instance_methods _((int, VALUE*, VALUE)); VALUE rb_class_public_instance_methods _((int, VALUE*, VALUE)); VALUE rb_class_protected_instance_methods _((int, VALUE*, VALUE)); VALUE rb_big_rshift(VALUE, VALUE); VALUE rb_class_private_instance_methods _((int, VALUE*, VALUE)); VALUE rb_obj_singleton_methods _((int, VALUE*, VALUE)); void rb_define_method_id _((VALUE, ID, VALUE (*)(ANYARGS), int)); void rb_frozen_class_p _((VALUE)); void rb_undef _((VALUE, ID)); void rb_define_protected_method _((VALUE, const char*, VALUE (*)(ANYARGS), int)); void rb_define_private_method _((VALUE, const char*, VALUE (*)(ANYARGS), int)); void rb_define_singleton_method _((VALUE, const char*, VALUE(*)(ANYARGS), int)); VALUE rb_singleton_class _((VALUE)); /* compar.c */ int rb_cmpint _((VALUE, VALUE, VALUE)); NORETURN(void rb_cmperr _((VALUE, VALUE))); /* enum.c */ /* error.c */ RUBY_EXTERN int ruby_nerrs; VALUE rb_exc_new _((VALUE, const char*, long)); VALUE rb_exc_new2 _((VALUE, const char*)); VALUE rb_exc_new3 _((VALUE, VALUE)); NORETURN(void rb_loaderror __((const char*, ...))); NORETURN(void rb_name_error __((ID, const char*, ...))); NORETURN(void rb_invalid_str _((const char*, const char*))); void rb_compile_error __((const char*, ...)); void rb_compile_error_append __((const char*, ...)); NORETURN(void rb_load_fail _((const char*))); NORETURN(void rb_error_frozen _((const char*))); void rb_check_frozen _((VALUE)); /* eval.c */ RUBY_EXTERN struct RNode *ruby_current_node; void ruby_set_current_source _((void)); NORETURN(void rb_exc_raise _((VALUE))); NORETURN(void rb_exc_fatal _((VALUE))); VALUE rb_f_exit _((int,VALUE*)); VALUE rb_f_abort _((int,VALUE*)); void rb_remove_method _((VALUE, const char*)); #define rb_disable_super(klass, name) ((void)0) #define rb_enable_super(klass, name) ((void)0) #define HAVE_RB_DEFINE_ALLOC_FUNC 1 void rb_define_alloc_func _((VALUE, VALUE (*)(VALUE))); void rb_undef_alloc_func _((VALUE)); void rb_clear_cache _((void)); void rb_clear_cache_by_class _((VALUE)); void rb_alias _((VALUE, ID, ID)); void rb_attr _((VALUE,ID,int,int,int)); int rb_method_boundp _((VALUE, ID, int)); VALUE rb_dvar_defined _((ID)); VALUE rb_dvar_curr _((ID)); VALUE rb_dvar_ref _((ID)); void rb_dvar_asgn _((ID, VALUE)); void rb_dvar_push _((ID, VALUE)); VALUE *rb_svar _((int)); VALUE rb_eval_cmd _((VALUE, VALUE, int)); int rb_obj_respond_to _((VALUE, ID, int)); int rb_respond_to _((VALUE, ID)); void rb_interrupt _((void)); VALUE rb_apply _((VALUE, ID, VALUE)); void rb_backtrace _((void)); ID rb_frame_last_func _((void)); VALUE rb_obj_instance_eval _((int, VALUE*, VALUE)); VALUE rb_mod_module_eval _((int, VALUE*, VALUE)); void rb_load _((VALUE, int)); void rb_load_protect _((VALUE, int, int*)); NORETURN(void rb_jump_tag _((int))); int rb_provided _((const char*)); void rb_provide _((const char*)); VALUE rb_f_require _((VALUE, VALUE)); VALUE rb_require_safe _((VALUE, int)); void rb_obj_call_init _((VALUE, int, VALUE*)); VALUE rb_class_new_instance _((int, VALUE*, VALUE)); VALUE rb_block_proc _((void)); VALUE rb_f_lambda _((void)); VALUE rb_proc_new _((VALUE (*)(ANYARGS/* VALUE yieldarg[, VALUE procarg] */), VALUE)); VALUE rb_protect _((VALUE (*)(VALUE), VALUE, int*)); void rb_set_end_proc _((void (*)(VALUE), VALUE)); void rb_mark_end_proc _((void)); void rb_exec_end_proc _((void)); void ruby_finalize _((void)); NORETURN(void ruby_stop _((int))); int ruby_cleanup _((int)); int ruby_exec _((void)); void rb_gc_mark_threads _((void)); void rb_thread_start_timer _((void)); void rb_thread_stop_timer _((void)); void rb_thread_schedule _((void)); void rb_thread_wait_fd _((int)); int rb_thread_fd_writable _((int)); void rb_thread_fd_close _((int)); int rb_thread_alone _((void)); void rb_thread_polling _((void)); void rb_thread_sleep _((int)); void rb_thread_sleep_forever _((void)); VALUE rb_thread_stop _((void)); VALUE rb_thread_wakeup _((VALUE)); VALUE rb_thread_wakeup_alive _((VALUE)); VALUE rb_thread_run _((VALUE)); VALUE rb_thread_kill _((VALUE)); VALUE rb_thread_alive_p _((VALUE)); VALUE rb_thread_create _((VALUE (*)(ANYARGS), void*)); void rb_thread_interrupt _((void)); void rb_thread_trap_eval _((VALUE, int, int)); void rb_thread_signal_raise _((int)); void rb_thread_signal_exit _((void)); int rb_thread_select _((int, fd_set *, fd_set *, fd_set *, struct timeval *)); void rb_thread_wait_for _((struct timeval)); VALUE rb_thread_current _((void)); VALUE rb_thread_main _((void)); VALUE rb_thread_local_aref _((VALUE, ID)); VALUE rb_thread_local_aset _((VALUE, ID, VALUE)); void rb_thread_atfork _((void)); VALUE rb_funcall_rescue __((VALUE, ID, int, ...)); /* file.c */ VALUE rb_file_s_expand_path _((int, VALUE *)); VALUE rb_file_expand_path _((VALUE, VALUE)); void rb_file_const _((const char*, VALUE)); int rb_find_file_ext _((VALUE*, const char* const*)); VALUE rb_find_file _((VALUE)); char *rb_path_next _((const char *)); char *rb_path_skip_prefix _((const char *)); char *rb_path_last_separator _((const char *)); char *rb_path_end _((const char *)); VALUE rb_file_directory_p _((VALUE,VALUE)); /* gc.c */ NORETURN(void rb_memerror __((void))); int ruby_stack_check _((void)); size_t ruby_stack_length _((VALUE**)); int rb_during_gc _((void)); char *rb_source_filename _((const char*)); void rb_gc_mark_locations _((VALUE*, VALUE*)); void rb_mark_tbl _((struct st_table*)); void rb_mark_set _((struct st_table*)); void rb_mark_hash _((struct st_table*)); void rb_gc_mark_maybe _((VALUE)); void rb_gc_mark _((VALUE)); void rb_gc_force_recycle _((VALUE)); void rb_gc _((void)); void rb_gc_copy_finalizer _((VALUE,VALUE)); void rb_gc_finalize_deferred _((void)); void rb_gc_call_finalizer_at_exit _((void)); VALUE rb_gc_enable _((void)); VALUE rb_gc_disable _((void)); VALUE rb_gc_start _((void)); /* hash.c */ void st_foreach_safe _((struct st_table *, int (*)(ANYARGS), unsigned long)); void rb_hash_foreach _((VALUE, int (*)(ANYARGS), VALUE)); VALUE rb_hash _((VALUE)); VALUE rb_hash_new _((void)); VALUE rb_hash_freeze _((VALUE)); VALUE rb_hash_aref _((VALUE, VALUE)); VALUE rb_hash_aset _((VALUE, VALUE, VALUE)); VALUE rb_hash_delete_if _((VALUE)); VALUE rb_hash_delete _((VALUE,VALUE)); int rb_path_check _((char*)); int rb_env_path_tainted _((void)); /* io.c */ #define rb_defout rb_stdout RUBY_EXTERN VALUE rb_fs; RUBY_EXTERN VALUE rb_output_fs; RUBY_EXTERN VALUE rb_rs; RUBY_EXTERN VALUE rb_default_rs; RUBY_EXTERN VALUE rb_output_rs; VALUE rb_io_write _((VALUE, VALUE)); VALUE rb_io_gets _((VALUE)); VALUE rb_io_getc _((VALUE)); VALUE rb_io_ungetc _((VALUE, VALUE)); VALUE rb_io_close _((VALUE)); VALUE rb_io_eof _((VALUE)); VALUE rb_io_binmode _((VALUE)); VALUE rb_io_addstr _((VALUE, VALUE)); VALUE rb_io_printf _((int, VALUE*, VALUE)); VALUE rb_io_print _((int, VALUE*, VALUE)); VALUE rb_io_puts _((int, VALUE*, VALUE)); VALUE rb_file_open _((const char*, const char*)); VALUE rb_gets _((void)); void rb_write_error _((const char*)); void rb_write_error2 _((const char*, long)); /* marshal.c */ VALUE rb_marshal_dump _((VALUE, VALUE)); VALUE rb_marshal_load _((VALUE)); /* numeric.c */ void rb_num_zerodiv _((void)); VALUE rb_num_coerce_bin _((VALUE, VALUE)); VALUE rb_num_coerce_cmp _((VALUE, VALUE)); VALUE rb_num_coerce_relop _((VALUE, VALUE)); VALUE rb_float_new _((double)); VALUE rb_num2fix _((VALUE)); VALUE rb_fix2str _((VALUE, int)); VALUE rb_dbl_cmp _((double, double)); /* object.c */ int rb_eql _((VALUE, VALUE)); VALUE rb_any_to_s _((VALUE)); VALUE rb_inspect _((VALUE)); VALUE rb_obj_is_instance_of _((VALUE, VALUE)); VALUE rb_obj_is_kind_of _((VALUE, VALUE)); VALUE rb_obj_alloc _((VALUE)); VALUE rb_obj_clone _((VALUE)); VALUE rb_obj_dup _((VALUE)); VALUE rb_obj_init_copy _((VALUE,VALUE)); VALUE rb_obj_taint _((VALUE)); VALUE rb_obj_tainted _((VALUE)); VALUE rb_obj_untaint _((VALUE)); VALUE rb_obj_freeze _((VALUE)); VALUE rb_obj_id _((VALUE)); VALUE rb_obj_class _((VALUE)); VALUE rb_class_real _((VALUE)); VALUE rb_class_inherited_p _((VALUE, VALUE)); VALUE rb_convert_type _((VALUE,int,const char*,const char*)); VALUE rb_check_convert_type _((VALUE,int,const char*,const char*)); VALUE rb_to_int _((VALUE)); VALUE rb_Integer _((VALUE)); VALUE rb_Float _((VALUE)); VALUE rb_String _((VALUE)); VALUE rb_Array _((VALUE)); double rb_cstr_to_dbl _((const char*, int)); double rb_str_to_dbl _((VALUE, int)); /* parse.y */ RUBY_EXTERN int ruby_sourceline; RUBY_EXTERN char *ruby_sourcefile; int ruby_yyparse _((void)); ID rb_id_attrset _((ID)); void rb_parser_append_print _((void)); void rb_parser_while_loop _((int, int)); int ruby_parser_stack_on_heap _((void)); void rb_gc_mark_parser _((void)); int rb_is_const_id _((ID)); int rb_is_instance_id _((ID)); int rb_is_class_id _((ID)); int rb_is_local_id _((ID)); int rb_is_junk_id _((ID)); int rb_symname_p _((const char*)); int rb_sym_interned_p _((VALUE)); VALUE rb_backref_get _((void)); void rb_backref_set _((VALUE)); VALUE rb_lastline_get _((void)); void rb_lastline_set _((VALUE)); VALUE rb_sym_all_symbols _((void)); /* process.c */ int rb_proc_exec _((const char*)); VALUE rb_f_exec _((int,VALUE*)); int rb_waitpid _((int,int*,int)); void rb_syswait _((int)); VALUE rb_proc_times _((VALUE)); VALUE rb_detach_process _((int)); /* range.c */ VALUE rb_range_new _((VALUE, VALUE, int)); VALUE rb_range_beg_len _((VALUE, long*, long*, long, int)); VALUE rb_length_by_each _((VALUE)); /* re.c */ int rb_memcmp _((const void*,const void*,long)); int rb_memcicmp _((const void*,const void*,long)); long rb_memsearch _((const void*,long,const void*,long)); VALUE rb_reg_nth_defined _((int, VALUE)); VALUE rb_reg_nth_match _((int, VALUE)); VALUE rb_reg_last_match _((VALUE)); VALUE rb_reg_match_pre _((VALUE)); VALUE rb_reg_match_post _((VALUE)); VALUE rb_reg_match_last _((VALUE)); VALUE rb_reg_new _((const char*, long, int)); VALUE rb_reg_match _((VALUE, VALUE)); VALUE rb_reg_match2 _((VALUE)); int rb_reg_options _((VALUE)); void rb_set_kcode _((const char*)); const char* rb_get_kcode _((void)); void rb_kcode_set_option _((VALUE)); void rb_kcode_reset_option _((void)); /* ruby.c */ RUBY_EXTERN VALUE rb_argv; RUBY_EXTERN VALUE rb_argv0; void rb_load_file _((const char*)); void ruby_script _((const char*)); void ruby_prog_init _((void)); void ruby_set_argv _((int, char**)); void ruby_process_options _((int, char**)); void ruby_load_script _((void)); void ruby_init_loadpath _((void)); void ruby_incpush _((const char*)); /* signal.c */ VALUE rb_f_kill _((int, VALUE*)); void rb_gc_mark_trap_list _((void)); #ifdef POSIX_SIGNAL #define posix_signal ruby_posix_signal void posix_signal _((int, RETSIGTYPE (*)(int))); #endif void rb_trap_exit _((void)); void rb_trap_exec _((void)); const char *ruby_signal_name _((int)); void ruby_default_signal _((int)); /* sprintf.c */ VALUE rb_f_sprintf _((int, VALUE*)); VALUE rb_str_format _((int, VALUE*, VALUE)); /* string.c */ VALUE rb_str_new _((const char*, long)); VALUE rb_str_new2 _((const char*)); VALUE rb_str_new3 _((VALUE)); VALUE rb_str_new4 _((VALUE)); VALUE rb_str_new5 _((VALUE, const char*, long)); VALUE rb_tainted_str_new _((const char*, long)); VALUE rb_tainted_str_new2 _((const char*)); VALUE rb_str_buf_new _((long)); VALUE rb_str_buf_new2 _((const char*)); VALUE rb_str_buf_append _((VALUE, VALUE)); VALUE rb_str_buf_cat _((VALUE, const char*, long)); VALUE rb_str_buf_cat2 _((VALUE, const char*)); VALUE rb_obj_as_string _((VALUE)); VALUE rb_check_string_type _((VALUE)); VALUE rb_str_dup _((VALUE)); VALUE rb_str_locktmp _((VALUE)); VALUE rb_str_unlocktmp _((VALUE)); VALUE rb_str_dup_frozen _((VALUE)); VALUE rb_str_plus _((VALUE, VALUE)); VALUE rb_str_times _((VALUE, VALUE)); VALUE rb_str_substr _((VALUE, long, long)); void rb_str_modify _((VALUE)); VALUE rb_str_freeze _((VALUE)); VALUE rb_str_resize _((VALUE, long)); VALUE rb_str_cat _((VALUE, const char*, long)); VALUE rb_str_cat2 _((VALUE, const char*)); VALUE rb_str_append _((VALUE, VALUE)); VALUE rb_str_concat _((VALUE, VALUE)); int rb_str_hash _((VALUE)); int rb_str_cmp _((VALUE, VALUE)); VALUE rb_str_upto _((VALUE, VALUE, int)); void rb_str_update _((VALUE, long, long, VALUE)); VALUE rb_str_inspect _((VALUE)); VALUE rb_str_dump _((VALUE)); VALUE rb_str_split _((VALUE, const char*)); void rb_str_associate _((VALUE, VALUE)); VALUE rb_str_associated _((VALUE)); void rb_str_setter _((VALUE, ID, VALUE*)); VALUE rb_str_intern _((VALUE)); /* struct.c */ VALUE rb_struct_new __((VALUE, ...)); VALUE rb_struct_define __((const char*, ...)); VALUE rb_struct_alloc _((VALUE, VALUE)); VALUE rb_struct_aref _((VALUE, VALUE)); VALUE rb_struct_aset _((VALUE, VALUE, VALUE)); VALUE rb_struct_getmember _((VALUE, ID)); VALUE rb_struct_iv_get _((VALUE, char*)); VALUE rb_struct_s_members _((VALUE)); VALUE rb_struct_members _((VALUE)); /* time.c */ VALUE rb_time_new _((time_t, time_t)); /* variable.c */ VALUE rb_mod_name _((VALUE)); VALUE rb_class_path _((VALUE)); void rb_set_class_path _((VALUE, VALUE, const char*)); VALUE rb_path2class _((const char*)); void rb_name_class _((VALUE, ID)); VALUE rb_class_name _((VALUE)); void rb_autoload _((VALUE, ID, const char*)); VALUE rb_autoload_load _((VALUE, ID)); VALUE rb_autoload_p _((VALUE, ID)); void rb_gc_mark_global_tbl _((void)); VALUE rb_f_trace_var _((int, VALUE*)); VALUE rb_f_untrace_var _((int, VALUE*)); VALUE rb_f_global_variables _((void)); void rb_alias_variable _((ID, ID)); struct st_table* rb_generic_ivar_table _((VALUE)); void rb_copy_generic_ivar _((VALUE,VALUE)); void rb_mark_generic_ivar _((VALUE)); void rb_mark_generic_ivar_tbl _((void)); void rb_free_generic_ivar _((VALUE)); VALUE rb_ivar_get _((VALUE, ID)); VALUE rb_ivar_set _((VALUE, ID, VALUE)); VALUE rb_ivar_defined _((VALUE, ID)); VALUE rb_iv_set _((VALUE, const char*, VALUE)); VALUE rb_iv_get _((VALUE, const char*)); VALUE rb_attr_get _((VALUE, ID)); VALUE rb_obj_instance_variables _((VALUE)); VALUE rb_obj_remove_instance_variable _((VALUE, VALUE)); void *rb_mod_const_at _((VALUE, void*)); void *rb_mod_const_of _((VALUE, void*)); VALUE rb_const_list _((void*)); VALUE rb_mod_constants _((VALUE)); VALUE rb_mod_remove_const _((VALUE, VALUE)); int rb_const_defined _((VALUE, ID)); int rb_const_defined_at _((VALUE, ID)); int rb_const_defined_from _((VALUE, ID)); VALUE rb_const_get _((VALUE, ID)); VALUE rb_const_get_at _((VALUE, ID)); VALUE rb_const_get_from _((VALUE, ID)); void rb_const_set _((VALUE, ID, VALUE)); VALUE rb_mod_constants _((VALUE)); VALUE rb_mod_const_missing _((VALUE,VALUE)); VALUE rb_cvar_defined _((VALUE, ID)); #define RB_CVAR_SET_4ARGS 1 void rb_cvar_set _((VALUE, ID, VALUE, int)); VALUE rb_cvar_get _((VALUE, ID)); void rb_cv_set _((VALUE, const char*, VALUE)); VALUE rb_cv_get _((VALUE, const char*)); void rb_define_class_variable _((VALUE, const char*, VALUE)); VALUE rb_mod_class_variables _((VALUE)); VALUE rb_mod_remove_cvar _((VALUE, VALUE)); /* version.c */ void ruby_show_version _((void)); void ruby_show_copyright _((void)); /************************************************ missing.h - prototype for *.c in ./missing, and for missing timeval struct $Author: shyouhei $ $Date: 2007-02-13 00:01:19 +0100 (Tue, 13 Feb 2007) $ created at: Sat May 11 23:46:03 JST 2002 ************************************************/ #ifndef MISSING_H #define MISSING_H #if defined(HAVE_SYS_TIME_H) # include #elif !defined(_WIN32) # define time_t long struct timeval { time_t tv_sec; /* seconds */ time_t tv_usec; /* microseconds */ }; #endif #if defined(HAVE_SYS_TYPES_H) # include #endif #ifndef HAVE_ACOSH extern double acosh _((double)); extern double asinh _((double)); extern double atanh _((double)); #endif #ifndef HAVE_CRYPT extern char *crypt _((char *, char *)); #endif #ifndef HAVE_DUP2 extern int dup2 _((int, int)); #endif #ifndef HAVE_EACCESS extern int eaccess _((const char*, int)); #endif #ifndef HAVE_FINITE extern int finite _((double)); #endif #ifndef HAVE_FLOCK extern int flock _((int, int)); #endif /* #ifndef HAVE_FREXP extern double frexp _((double, int *)); #endif */ #ifndef HAVE_HYPOT extern double hypot _((double, double)); #endif #ifndef HAVE_ERF extern double erf _((double)); extern double erfc _((double)); #endif #ifndef HAVE_ISINF # if defined(HAVE_FINITE) && defined(HAVE_ISNAN) # define isinf(x) (!finite(x) && !isnan(x)) # else extern int isinf _((double)); # endif #endif #ifndef HAVE_ISNAN extern int isnan _((double)); #endif /* #ifndef HAVE_MEMCMP extern int memcmp _((char *, char *, int)); #endif */ #ifndef HAVE_MEMMOVE extern void *memmove _((void *, void *, int)); #endif /* #ifndef HAVE_MODF extern double modf _((double, double *)); #endif */ #ifndef HAVE_STRCASECMP extern int strcasecmp _((char *, char *)); #endif #ifndef HAVE_STRNCASECMP extern int strncasecmp _((char *, char *, int)); #endif #ifndef HAVE_STRCHR extern char *strchr _((char *, int)); extern char *strrchr _((char *, int)); #endif #ifndef HAVE_STRERROR extern char *strerror _((int)); #endif #ifndef HAVE_STRFTIME extern size_t strftime _((char *, size_t, const char *, const struct tm *)); #endif #ifndef HAVE_STRSTR extern char *strstr _((char *, char *)); #endif /* #ifndef HAVE_STRTOL extern long strtol _((char *, char **, int)); #endif */ #ifndef HAVE_STRTOUL extern unsigned long strtoul _((char *, char **, int)); #endif #ifndef HAVE_VSNPRINTF # ifdef HAVE_STDARG_PROTOTYPES # include # else # include # endif extern int snprintf __((char *, size_t n, char const *, ...)); extern int vsnprintf _((char *, size_t n, char const *, va_list)); #endif #endif /* MISSING_H */ /********************************************************************** node.h - $Author: wyhaines $ $Date: 2009-07-09 20:16:38 +0200 (Thu, 09 Jul 2009) $ created at: Fri May 28 15:14:02 JST 1993 Copyright (C) 1993-2003 Yukihiro Matsumoto **********************************************************************/ #ifndef NODE_H #define NODE_H #if defined(__cplusplus) extern "C" { #endif enum node_type { NODE_METHOD, NODE_FBODY, NODE_CFUNC, NODE_SCOPE, NODE_BLOCK, NODE_IF, NODE_CASE, NODE_WHEN, NODE_OPT_N, NODE_WHILE, NODE_UNTIL, NODE_ITER, NODE_FOR, NODE_BREAK, NODE_NEXT, NODE_REDO, NODE_RETRY, NODE_BEGIN, NODE_RESCUE, NODE_RESBODY, NODE_ENSURE, NODE_AND, NODE_OR, NODE_NOT, NODE_MASGN, NODE_LASGN, NODE_DASGN, NODE_DASGN_CURR, NODE_GASGN, NODE_IASGN, NODE_CDECL, NODE_CVASGN, NODE_CVDECL, NODE_OP_ASGN1, NODE_OP_ASGN2, NODE_OP_ASGN_AND, NODE_OP_ASGN_OR, NODE_CALL, NODE_FCALL, NODE_VCALL, NODE_SUPER, NODE_ZSUPER, NODE_ARRAY, NODE_ZARRAY, NODE_HASH, NODE_RETURN, NODE_YIELD, NODE_LVAR, NODE_DVAR, NODE_GVAR, NODE_IVAR, NODE_CONST, NODE_CVAR, NODE_NTH_REF, NODE_BACK_REF, NODE_MATCH, NODE_MATCH2, NODE_MATCH3, NODE_LIT, NODE_STR, NODE_DSTR, NODE_XSTR, NODE_DXSTR, NODE_EVSTR, NODE_DREGX, NODE_DREGX_ONCE, NODE_ARGS, NODE_ARGSCAT, NODE_ARGSPUSH, NODE_SPLAT, NODE_TO_ARY, NODE_SVALUE, NODE_BLOCK_ARG, NODE_BLOCK_PASS, NODE_DEFN, NODE_DEFS, NODE_ALIAS, NODE_VALIAS, NODE_UNDEF, NODE_CLASS, NODE_MODULE, NODE_SCLASS, NODE_COLON2, NODE_COLON3, NODE_CREF, NODE_DOT2, NODE_DOT3, NODE_FLIP2, NODE_FLIP3, NODE_ATTRSET, NODE_SELF, NODE_NIL, NODE_TRUE, NODE_FALSE, NODE_DEFINED, NODE_NEWLINE, NODE_POSTEXE, NODE_ALLOCA, NODE_DMETHOD, NODE_BMETHOD, NODE_MEMO, NODE_IFUNC, NODE_DSYM, NODE_ATTRASGN, NODE_LAST }; typedef struct RNode { unsigned long flags; char *nd_file; union { struct RNode *node; ID id; VALUE value; VALUE (*cfunc)(ANYARGS); ID *tbl; } u1; union { struct RNode *node; ID id; long argc; VALUE value; } u2; union { struct RNode *node; ID id; long state; struct global_entry *entry; long cnt; VALUE value; } u3; } NODE; extern NODE *ruby_cref; extern NODE *ruby_top_cref; #define RNODE(obj) (R_CAST(RNode)(obj)) #define nd_type(n) ((int)(((RNODE(n))->flags>>FL_USHIFT)&0xff)) #define nd_set_type(n,t) \ RNODE(n)->flags=((RNODE(n)->flags&~FL_UMASK)|(((t)<flags>>NODE_LSHIFT)&NODE_LMASK)) #define nd_set_line(n,l) \ RNODE(n)->flags=((RNODE(n)->flags&~(-1< #define USE_CONTEXT #endif #include #include "st.h" #ifdef USE_CONTEXT typedef struct { ucontext_t context; volatile int status; } rb_jmpbuf_t[1]; #else typedef jmp_buf rb_jmpbuf_t; #endif enum rb_thread_status { THREAD_TO_KILL, THREAD_RUNNABLE, THREAD_STOPPED, THREAD_KILLED }; typedef struct rb_thread *rb_thread_t; struct rb_thread { rb_thread_t next, prev; rb_jmpbuf_t context; #if (defined _WIN32 && !defined _WIN32_WCE) || defined __CYGWIN__ unsigned long win32_exception_list; #endif VALUE result; size_t stk_len; size_t stk_max; VALUE *stk_ptr; VALUE *stk_pos; #ifdef __ia64 size_t bstr_len; size_t bstr_max; VALUE *bstr_ptr; VALUE *bstr_pos; #endif struct FRAME *frame; struct SCOPE *scope; struct RVarmap *dyna_vars; struct BLOCK *block; struct iter *iter; struct tag *tag; VALUE klass; VALUE wrapper; NODE *cref; int flags; /* misc. states (vmode/rb_trap_immediate/raised) */ NODE *node; int tracing; VALUE errinfo; VALUE last_status; VALUE last_line; VALUE last_match; int safe; enum rb_thread_status status; int wait_for; int fd; fd_set readfds; fd_set writefds; fd_set exceptfds; int select_value; double delay; rb_thread_t join; int abort; int priority; VALUE thgroup; struct st_table *locals; VALUE thread; VALUE sandbox; }; extern VALUE (*ruby_sandbox_save)_((rb_thread_t)); extern VALUE (*ruby_sandbox_restore)_((rb_thread_t)); extern rb_thread_t rb_curr_thread; extern rb_thread_t rb_main_thread; enum { RAISED_EXCEPTION = 0x1000, RAISED_STACKOVERFLOW = 0x2000, RAISED_NOMEMORY = 0x4000, RAISED_MASK = 0xf000 }; int rb_thread_set_raised(rb_thread_t th); int rb_thread_reset_raised(rb_thread_t th); #define rb_thread_raised_set(th, f) ((th)->flags |= (f)) #define rb_thread_raised_reset(th, f) ((th)->flags &= ~(f)) #define rb_thread_raised_p(th, f) (((th)->flags & (f)) != 0) #define rb_thread_raised_clear(th) (rb_thread_raised_reset(th, RAISED_MASK)) #if defined(__cplusplus) } /* extern "C" { */ #endif #endif /********************************************************************** re.h - $Author: shyouhei $ $Date: 2007-02-13 00:01:19 +0100 (Tue, 13 Feb 2007) $ created at: Thu Sep 30 14:18:32 JST 1993 Copyright (C) 1993-2003 Yukihiro Matsumoto **********************************************************************/ #ifndef RE_H #define RE_H #include #include #include "regex.h" typedef struct re_pattern_buffer Regexp; struct RMatch { struct RBasic basic; VALUE str; struct re_registers *regs; }; #define RMATCH(obj) (R_CAST(RMatch)(obj)) VALUE rb_reg_regcomp _((VALUE)); long rb_reg_search _((VALUE, VALUE, long, long)); VALUE rb_reg_regsub _((VALUE, VALUE, struct re_registers *)); long rb_reg_adjust_startpos _((VALUE, VALUE, long, long)); void rb_match_busy _((VALUE)); VALUE rb_reg_quote _((VALUE)); RUBY_EXTERN int ruby_ignorecase; int rb_reg_mbclen2 _((unsigned int, VALUE)); #define mbclen2(c,re) rb_reg_mbclen2((c),(re)) #endif /* Definitions for data structures and routines for the regular expression library, version 0.12. Copyright (C) 1985,89,90,91,92,93,95,96,97,98 Free Software Foundation, Inc. This file is part of the GNU C Library. Its master source is NOT part of the C library, however. The master source lives in /gd/gnu/lib. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. The GNU C Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with the GNU C Library; see the file LGPL. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* Multi-byte extension added May, 1993 by t^2 (Takahiro Tanimoto) Last change: May 21, 1993 by t^2 */ /* modified for Ruby by matz@netlab.co.jp */ #ifndef REGEX_H #define REGEX_H /* symbol mangling for ruby */ #ifdef RUBY # define re_adjust_startpos ruby_re_adjust_startpos # define re_compile_fastmap ruby_re_compile_fastmap # define re_compile_pattern ruby_re_compile_pattern # define re_copy_registers ruby_re_copy_registers # define re_free_pattern ruby_re_free_pattern # define re_free_registers ruby_re_free_registers # define re_match ruby_re_match # define re_mbcinit ruby_re_mbcinit # define re_search ruby_re_search # define re_set_casetable ruby_re_set_casetable # define register_info_type ruby_register_info_type #endif #include /* Define number of parens for which we record the beginnings and ends. This affects how much space the `struct re_registers' type takes up. */ #ifndef RE_NREGS #define RE_NREGS 10 #endif #define BYTEWIDTH 8 #define RE_REG_MAX ((1< #endif #ifdef HAVE_STRING_H # include #else # include #endif #ifdef HAVE_INTRINSICS_H # include #endif #include #include /* need to include to use these macros */ #ifndef ISPRINT #define ISASCII(c) isascii((int)(unsigned char)(c)) #undef ISPRINT #define ISPRINT(c) (ISASCII(c) && isprint((int)(unsigned char)(c))) #define ISSPACE(c) (ISASCII(c) && isspace((int)(unsigned char)(c))) #define ISUPPER(c) (ISASCII(c) && isupper((int)(unsigned char)(c))) #define ISLOWER(c) (ISASCII(c) && islower((int)(unsigned char)(c))) #define ISALNUM(c) (ISASCII(c) && isalnum((int)(unsigned char)(c))) #define ISALPHA(c) (ISASCII(c) && isalpha((int)(unsigned char)(c))) #define ISDIGIT(c) (ISASCII(c) && isdigit((int)(unsigned char)(c))) #define ISXDIGIT(c) (ISASCII(c) && isxdigit((int)(unsigned char)(c))) #endif #if defined(HAVE_ALLOCA_H) #include #else # ifdef _AIX #pragma alloca # endif #endif #if defined(__VMS) # pragma builtins # define alloca __alloca #endif #if SIZEOF_LONG != SIZEOF_VOIDP # error ---->> ruby requires sizeof(void*) == sizeof(long) to be compiled. <<---- #else typedef unsigned long VALUE; typedef unsigned long ID; #endif #ifdef __STDC__ # include #else # ifndef LONG_MAX # ifdef HAVE_LIMITS_H # include # else /* assuming 32bit(2's compliment) long */ # define LONG_MAX 2147483647 # endif # endif # ifndef LONG_MIN # define LONG_MIN (-LONG_MAX-1) # endif # ifndef CHAR_BIT # define CHAR_BIT 8 # endif #endif #ifdef HAVE_LONG_LONG # ifndef LLONG_MAX # ifdef LONG_LONG_MAX # define LLONG_MAX LONG_LONG_MAX # else # ifdef _I64_MAX # define LLONG_MAX _I64_MAX # else /* assuming 64bit(2's complement) long long */ # define LLONG_MAX 9223372036854775807LL # endif # endif # endif # ifndef LLONG_MIN # ifdef LONG_LONG_MIN # define LLONG_MIN LONG_LONG_MIN # else # ifdef _I64_MIN # define LLONG_MIN _I64_MIN # else # define LLONG_MIN (-LLONG_MAX-1) # endif # endif # endif #endif #define FIXNUM_MAX (LONG_MAX>>1) #define FIXNUM_MIN RSHIFT((long)LONG_MIN,1) #define FIXNUM_FLAG 0x01 #define INT2FIX(i) ((VALUE)(((long)(i))<<1 | FIXNUM_FLAG)) #define LONG2FIX(i) INT2FIX(i) #define rb_fix_new(v) INT2FIX(v) VALUE rb_int2inum _((long)); #define INT2NUM(v) rb_int2inum(v) #define LONG2NUM(v) INT2NUM(v) #define rb_int_new(v) rb_int2inum(v) VALUE rb_uint2inum _((unsigned long)); #define UINT2NUM(v) rb_uint2inum(v) #define ULONG2NUM(v) UINT2NUM(v) #define rb_uint_new(v) rb_uint2inum(v) #ifdef HAVE_LONG_LONG VALUE rb_ll2inum _((LONG_LONG)); #define LL2NUM(v) rb_ll2inum(v) VALUE rb_ull2inum _((unsigned LONG_LONG)); #define ULL2NUM(v) rb_ull2inum(v) #endif #if SIZEOF_OFF_T > SIZEOF_LONG && defined(HAVE_LONG_LONG) # define OFFT2NUM(v) LL2NUM(v) #elif SIZEOF_OFF_T == SIZEOF_LONG # define OFFT2NUM(v) LONG2NUM(v) #else # define OFFT2NUM(v) INT2NUM(v) #endif #define FIX2LONG(x) RSHIFT((long)x,1) #define FIX2ULONG(x) (((unsigned long)(x))>>1) #define FIXNUM_P(f) (((long)(f))&FIXNUM_FLAG) #define POSFIXABLE(f) ((f) < FIXNUM_MAX+1) #define NEGFIXABLE(f) ((f) >= FIXNUM_MIN) #define FIXABLE(f) (POSFIXABLE(f) && NEGFIXABLE(f)) #define IMMEDIATE_MASK 0x03 #define IMMEDIATE_P(x) ((VALUE)(x) & IMMEDIATE_MASK) #define SYMBOL_FLAG 0x0e #define SYMBOL_P(x) (((VALUE)(x)&0xff)==SYMBOL_FLAG) #define ID2SYM(x) ((VALUE)(((long)(x))<<8|SYMBOL_FLAG)) #define SYM2ID(x) RSHIFT((unsigned long)x,8) /* special contants - i.e. non-zero and non-fixnum constants */ #define Qfalse ((VALUE)0) #define Qtrue ((VALUE)2) #define Qnil ((VALUE)4) #define Qundef ((VALUE)6) /* undefined value for placeholder */ #define RTEST(v) (((VALUE)(v) & ~Qnil) != 0) #define NIL_P(v) ((VALUE)(v) == Qnil) #define CLASS_OF(v) rb_class_of((VALUE)(v)) #define T_NONE 0x00 #define T_NIL 0x01 #define T_OBJECT 0x02 #define T_CLASS 0x03 #define T_ICLASS 0x04 #define T_MODULE 0x05 #define T_FLOAT 0x06 #define T_STRING 0x07 #define T_REGEXP 0x08 #define T_ARRAY 0x09 #define T_FIXNUM 0x0a #define T_HASH 0x0b #define T_STRUCT 0x0c #define T_BIGNUM 0x0d #define T_FILE 0x0e #define T_TRUE 0x20 #define T_FALSE 0x21 #define T_DATA 0x22 #define T_MATCH 0x23 #define T_SYMBOL 0x24 #define T_BLKTAG 0x3b #define T_UNDEF 0x3c #define T_VARMAP 0x3d #define T_SCOPE 0x3e #define T_NODE 0x3f #define T_MASK 0x3f #define BUILTIN_TYPE(x) (((struct RBasic*)(x))->flags & T_MASK) #define TYPE(x) rb_type((VALUE)(x)) void rb_check_type _((VALUE,int)); #define Check_Type(v,t) rb_check_type((VALUE)(v),t) VALUE rb_str_to_str _((VALUE)); VALUE rb_string_value _((volatile VALUE*)); char *rb_string_value_ptr _((volatile VALUE*)); char *rb_string_value_cstr _((volatile VALUE*)); #define StringValue(v) rb_string_value(&(v)) #define StringValuePtr(v) rb_string_value_ptr(&(v)) #define StringValueCStr(v) rb_string_value_cstr(&(v)) void rb_check_safe_obj _((VALUE)); void rb_check_safe_str _((VALUE)); #define SafeStringValue(v) do {\ StringValue(v);\ rb_check_safe_obj(v);\ } while (0) /* obsolete macro - use SafeStringValue(v) */ #define Check_SafeStr(v) rb_check_safe_str((VALUE)(v)) void rb_secure _((int)); RUBY_EXTERN int ruby_safe_level; #define rb_safe_level() (ruby_safe_level) void rb_set_safe_level _((int)); void rb_secure_update _((VALUE)); long rb_num2long _((VALUE)); unsigned long rb_num2ulong _((VALUE)); #define NUM2LONG(x) (FIXNUM_P(x)?FIX2LONG(x):rb_num2long((VALUE)x)) #define NUM2ULONG(x) rb_num2ulong((VALUE)x) #if SIZEOF_INT < SIZEOF_LONG long rb_num2int _((VALUE)); #define NUM2INT(x) (FIXNUM_P(x)?FIX2INT(x):rb_num2int((VALUE)x)) long rb_fix2int _((VALUE)); #define FIX2INT(x) rb_fix2int((VALUE)x) unsigned long rb_num2uint _((VALUE)); #define NUM2UINT(x) rb_num2uint(x) unsigned long rb_fix2uint _((VALUE)); #define FIX2UINT(x) rb_fix2uint(x) #else #define NUM2INT(x) ((int)NUM2LONG(x)) #define NUM2UINT(x) ((unsigned int)NUM2ULONG(x)) #define FIX2INT(x) ((int)FIX2LONG(x)) #define FIX2UINT(x) ((unsigned int)FIX2ULONG(x)) #endif #ifdef HAVE_LONG_LONG LONG_LONG rb_num2ll _((VALUE)); unsigned LONG_LONG rb_num2ull _((VALUE)); # define NUM2LL(x) (FIXNUM_P(x)?FIX2LONG(x):rb_num2ll((VALUE)x)) # define NUM2ULL(x) rb_num2ull((VALUE)x) #endif #if defined(HAVE_LONG_LONG) && SIZEOF_OFF_T > SIZEOF_LONG # define NUM2OFFT(x) ((off_t)NUM2LL(x)) #else # define NUM2OFFT(x) NUM2LONG(x) #endif double rb_num2dbl _((VALUE)); #define NUM2DBL(x) rb_num2dbl((VALUE)(x)) /* obsolete API - use StringValue() */ char *rb_str2cstr _((VALUE,long*)); /* obsolete API - use StringValuePtr() */ #define STR2CSTR(x) rb_str2cstr((VALUE)(x),0) #define NUM2CHR(x) (((TYPE(x) == T_STRING)&&(RSTRING(x)->len>=1))?\ RSTRING(x)->ptr[0]:(char)(NUM2INT(x)&0xff)) #define CHR2FIX(x) INT2FIX((long)((x)&0xff)) VALUE rb_newobj _((void)); #define NEWOBJ(obj,type) type *obj = (type*)rb_newobj() #define OBJSETUP(obj,c,t) do {\ RBASIC(obj)->flags = (t);\ RBASIC(obj)->klass = (c);\ if (rb_safe_level() >= 3) FL_SET(obj, FL_TAINT);\ } while (0) #define CLONESETUP(clone,obj) do {\ OBJSETUP(clone,rb_singleton_class_clone((VALUE)obj),RBASIC(obj)->flags);\ rb_singleton_class_attached(RBASIC(clone)->klass, (VALUE)clone);\ if (FL_TEST(obj, FL_EXIVAR)) rb_copy_generic_ivar((VALUE)clone,(VALUE)obj);\ } while (0) #define DUPSETUP(dup,obj) do {\ OBJSETUP(dup,rb_obj_class(obj),(RBASIC(obj)->flags)&(T_MASK|FL_EXIVAR|FL_TAINT));\ if (FL_TEST(obj, FL_EXIVAR)) rb_copy_generic_ivar((VALUE)dup,(VALUE)obj);\ } while (0) struct RBasic { unsigned long flags; VALUE klass; }; struct RObject { struct RBasic basic; struct st_table *iv_tbl; }; struct RClass { struct RBasic basic; struct st_table *iv_tbl; struct st_table *m_tbl; VALUE super; }; struct RFloat { struct RBasic basic; double value; }; #define ELTS_SHARED FL_USER2 struct RString { struct RBasic basic; long len; char *ptr; union { long capa; VALUE shared; } aux; }; #define RSTRING_PTR(s) (RSTRING(s)->ptr) #define RSTRING_LEN(s) (RSTRING(s)->len) struct RArray { struct RBasic basic; long len; union { long capa; VALUE shared; } aux; VALUE *ptr; }; #define RARRAY_PTR(s) (RARRAY(s)->ptr) #define RARRAY_LEN(s) (RARRAY(s)->len) struct RRegexp { struct RBasic basic; struct re_pattern_buffer *ptr; long len; char *str; }; struct RHash { struct RBasic basic; struct st_table *tbl; int iter_lev; VALUE ifnone; }; struct RFile { struct RBasic basic; struct OpenFile *fptr; }; struct RData { struct RBasic basic; void (*dmark) _((void*)); void (*dfree) _((void*)); void *data; }; #define DATA_PTR(dta) (RDATA(dta)->data) /* #define RUBY_DATA_FUNC(func) ((void (*)_((void*)))func) */ typedef void (*RUBY_DATA_FUNC) _((void*)); VALUE rb_data_object_alloc _((VALUE,void*,RUBY_DATA_FUNC,RUBY_DATA_FUNC)); #define Data_Wrap_Struct(klass,mark,free,sval)\ rb_data_object_alloc(klass,sval,(RUBY_DATA_FUNC)mark,(RUBY_DATA_FUNC)free) #define Data_Make_Struct(klass,type,mark,free,sval) (\ sval = ALLOC(type),\ memset(sval, 0, sizeof(type)),\ Data_Wrap_Struct(klass,mark,free,sval)\ ) #define Data_Get_Struct(obj,type,sval) do {\ Check_Type(obj, T_DATA); \ sval = (type*)DATA_PTR(obj);\ } while (0) struct RStruct { struct RBasic basic; long len; VALUE *ptr; }; #define RSTRUCT_LEN(st) (RSTRUCT(st)->len) #define RSTRUCT_PTR(st) (RSTRUCT(st)->ptr) struct RBignum { struct RBasic basic; char sign; long len; void *digits; }; #define R_CAST(st) (struct st*) #define RBASIC(obj) (R_CAST(RBasic)(obj)) #define ROBJECT(obj) (R_CAST(RObject)(obj)) #define RCLASS(obj) (R_CAST(RClass)(obj)) #define RMODULE(obj) RCLASS(obj) #define RFLOAT(obj) (R_CAST(RFloat)(obj)) #define RSTRING(obj) (R_CAST(RString)(obj)) #define RREGEXP(obj) (R_CAST(RRegexp)(obj)) #define RARRAY(obj) (R_CAST(RArray)(obj)) #define RHASH(obj) (R_CAST(RHash)(obj)) #define RDATA(obj) (R_CAST(RData)(obj)) #define RSTRUCT(obj) (R_CAST(RStruct)(obj)) #define RBIGNUM(obj) (R_CAST(RBignum)(obj)) #define RFILE(obj) (R_CAST(RFile)(obj)) #define FL_SINGLETON FL_USER0 #define FL_MARK (1<<6) #define FL_FINALIZE (1<<7) #define FL_TAINT (1<<8) #define FL_EXIVAR (1<<9) #define FL_FREEZE (1<<10) #define FL_USHIFT 11 #define FL_USER0 (1<<(FL_USHIFT+0)) #define FL_USER1 (1<<(FL_USHIFT+1)) #define FL_USER2 (1<<(FL_USHIFT+2)) #define FL_USER3 (1<<(FL_USHIFT+3)) #define FL_USER4 (1<<(FL_USHIFT+4)) #define FL_USER5 (1<<(FL_USHIFT+5)) #define FL_USER6 (1<<(FL_USHIFT+6)) #define FL_USER7 (1<<(FL_USHIFT+7)) #define FL_UMASK (0xff<flags&(f)):0) #define FL_SET(x,f) do {if (FL_ABLE(x)) RBASIC(x)->flags |= (f);} while (0) #define FL_UNSET(x,f) do {if (FL_ABLE(x)) RBASIC(x)->flags &= ~(f);} while (0) #define FL_REVERSE(x,f) do {if (FL_ABLE(x)) RBASIC(x)->flags ^= (f);} while (0) #define OBJ_TAINTED(x) FL_TEST((x), FL_TAINT) #define OBJ_TAINT(x) FL_SET((x), FL_TAINT) #define OBJ_INFECT(x,s) do {if (FL_ABLE(x) && FL_ABLE(s)) RBASIC(x)->flags |= RBASIC(s)->flags & FL_TAINT;} while (0) #define OBJ_FROZEN(x) FL_TEST((x), FL_FREEZE) #define OBJ_FREEZE(x) FL_SET((x), FL_FREEZE) #define ALLOC_N(type,n) (type*)xmalloc(sizeof(type)*(n)) #define ALLOC(type) (type*)xmalloc(sizeof(type)) #define REALLOC_N(var,type,n) (var)=(type*)xrealloc((char*)(var),sizeof(type)*(n)) #define ALLOCA_N(type,n) (type*)alloca(sizeof(type)*(n)) #define MEMZERO(p,type,n) memset((p), 0, sizeof(type)*(n)) #define MEMCPY(p1,p2,type,n) memcpy((p1), (p2), sizeof(type)*(n)) #define MEMMOVE(p1,p2,type,n) memmove((p1), (p2), sizeof(type)*(n)) #define MEMCMP(p1,p2,type,n) memcmp((p1), (p2), sizeof(type)*(n)) void rb_obj_infect _((VALUE,VALUE)); typedef int ruby_glob_func(const char*,VALUE); void rb_glob _((const char*,void(*)(const char*,VALUE),VALUE)); void rb_globi _((const char*,void(*)(const char*,VALUE),VALUE)); int ruby_brace_expand _((const char*,int,ruby_glob_func*,VALUE)); int ruby_brace_glob _((const char*,int,ruby_glob_func*,VALUE)); VALUE rb_define_class _((const char*,VALUE)); VALUE rb_define_module _((const char*)); VALUE rb_define_class_under _((VALUE, const char*, VALUE)); VALUE rb_define_module_under _((VALUE, const char*)); void rb_include_module _((VALUE,VALUE)); void rb_extend_object _((VALUE,VALUE)); void rb_define_variable _((const char*,VALUE*)); void rb_define_virtual_variable _((const char*,VALUE(*)(ANYARGS),void(*)(ANYARGS))); void rb_define_hooked_variable _((const char*,VALUE*,VALUE(*)(ANYARGS),void(*)(ANYARGS))); int ruby_glob _((const char*,int,int(*)(const char*,VALUE),VALUE)); int ruby_globi _((const char*,int,int(*)(const char*,VALUE),VALUE)); void rb_define_readonly_variable _((const char*,VALUE*)); void rb_define_const _((VALUE,const char*,VALUE)); void rb_define_global_const _((const char*,VALUE)); #define RUBY_METHOD_FUNC(func) ((VALUE (*)(ANYARGS))func) void rb_define_method _((VALUE,const char*,VALUE(*)(ANYARGS),int)); void rb_define_module_function _((VALUE,const char*,VALUE(*)(ANYARGS),int)); void rb_define_global_function _((const char*,VALUE(*)(ANYARGS),int)); void rb_undef_method _((VALUE,const char*)); void rb_define_alias _((VALUE,const char*,const char*)); void rb_define_attr _((VALUE,const char*,int,int)); void rb_global_variable _((VALUE*)); void rb_gc_register_address _((VALUE*)); void rb_gc_unregister_address _((VALUE*)); ID rb_intern _((const char*)); char *rb_id2name _((ID)); ID rb_to_id _((VALUE)); char *rb_class2name _((VALUE)); char *rb_obj_classname _((VALUE)); void rb_p _((VALUE)); VALUE rb_eval_string _((const char*)); VALUE rb_eval_string_protect _((const char*, int*)); VALUE rb_eval_string_wrap _((const char*, int*)); VALUE rb_funcall __((VALUE, ID, int, ...)); VALUE rb_funcall2 _((VALUE, ID, int, const VALUE*)); VALUE rb_funcall3 _((VALUE, ID, int, const VALUE*)); int rb_scan_args __((int, const VALUE*, const char*, ...)); VALUE rb_call_super _((int, const VALUE*)); VALUE rb_gv_set _((const char*, VALUE)); VALUE rb_gv_get _((const char*)); VALUE rb_iv_get _((VALUE, const char*)); VALUE rb_iv_set _((VALUE, const char*, VALUE)); VALUE rb_equal _((VALUE,VALUE)); RUBY_EXTERN VALUE ruby_verbose, ruby_debug; NORETURN(void rb_raise __((VALUE, const char*, ...))); NORETURN(void rb_fatal __((const char*, ...))); NORETURN(void rb_bug __((const char*, ...))); NORETURN(void rb_sys_fail _((const char*))); NORETURN(void rb_iter_break _((void))); NORETURN(void rb_exit _((int))); NORETURN(void rb_notimplement _((void))); void rb_warning __((const char*, ...)); /* reports if `-w' specified */ void rb_sys_warning __((const char*, ...)); /* reports if `-w' specified */ void rb_warn __((const char*, ...)); /* reports always */ VALUE rb_each _((VALUE)); VALUE rb_yield _((VALUE)); VALUE rb_yield_values __((int n, ...)); VALUE rb_yield_splat _((VALUE)); int rb_block_given_p _((void)); void rb_need_block _((void)); VALUE rb_iterate _((VALUE(*)(VALUE),VALUE,VALUE(*)(ANYARGS),VALUE)); VALUE rb_rescue _((VALUE(*)(ANYARGS),VALUE,VALUE(*)(ANYARGS),VALUE)); VALUE rb_rescue2 __((VALUE(*)(ANYARGS),VALUE,VALUE(*)(ANYARGS),VALUE,...)); VALUE rb_ensure _((VALUE(*)(ANYARGS),VALUE,VALUE(*)(ANYARGS),VALUE)); VALUE rb_catch _((const char*,VALUE(*)(ANYARGS),VALUE)); NORETURN(void rb_throw _((const char*,VALUE))); VALUE rb_require _((const char*)); #ifdef __ia64 void ruby_init_stack(VALUE*, void*); #define RUBY_INIT_STACK \ VALUE variable_in_this_stack_frame; \ ruby_init_stack(&variable_in_this_stack_frame, rb_ia64_bsp()); #else void ruby_init_stack(VALUE*); #define RUBY_INIT_STACK \ VALUE variable_in_this_stack_frame; \ ruby_init_stack(&variable_in_this_stack_frame); #endif void ruby_init _((void)); void ruby_options _((int, char**)); NORETURN(void ruby_run _((void))); RUBY_EXTERN VALUE rb_mKernel; RUBY_EXTERN VALUE rb_mComparable; RUBY_EXTERN VALUE rb_mEnumerable; RUBY_EXTERN VALUE rb_mPrecision; RUBY_EXTERN VALUE rb_mErrno; RUBY_EXTERN VALUE rb_mFileTest; RUBY_EXTERN VALUE rb_mGC; RUBY_EXTERN VALUE rb_mMath; RUBY_EXTERN VALUE rb_mProcess; RUBY_EXTERN VALUE rb_cObject; RUBY_EXTERN VALUE rb_cArray; RUBY_EXTERN VALUE rb_cBignum; RUBY_EXTERN VALUE rb_cBinding; RUBY_EXTERN VALUE rb_cClass; RUBY_EXTERN VALUE rb_cCont; RUBY_EXTERN VALUE rb_cDir; RUBY_EXTERN VALUE rb_cData; RUBY_EXTERN VALUE rb_cFalseClass; RUBY_EXTERN VALUE rb_cFile; RUBY_EXTERN VALUE rb_cFixnum; RUBY_EXTERN VALUE rb_cFloat; RUBY_EXTERN VALUE rb_cHash; RUBY_EXTERN VALUE rb_cInteger; RUBY_EXTERN VALUE rb_cIO; RUBY_EXTERN VALUE rb_cMatch; RUBY_EXTERN VALUE rb_cMethod; RUBY_EXTERN VALUE rb_cModule; RUBY_EXTERN VALUE rb_cNameErrorMesg; RUBY_EXTERN VALUE rb_cNilClass; RUBY_EXTERN VALUE rb_cNumeric; RUBY_EXTERN VALUE rb_cProc; RUBY_EXTERN VALUE rb_cRange; RUBY_EXTERN VALUE rb_cRegexp; RUBY_EXTERN VALUE rb_cStat; RUBY_EXTERN VALUE rb_cString; RUBY_EXTERN VALUE rb_cStruct; RUBY_EXTERN VALUE rb_cSymbol; RUBY_EXTERN VALUE rb_cThread; RUBY_EXTERN VALUE rb_cTime; RUBY_EXTERN VALUE rb_cTrueClass; RUBY_EXTERN VALUE rb_cUnboundMethod; RUBY_EXTERN VALUE rb_eException; RUBY_EXTERN VALUE rb_eStandardError; RUBY_EXTERN VALUE rb_eSystemExit; RUBY_EXTERN VALUE rb_eInterrupt; RUBY_EXTERN VALUE rb_eSignal; RUBY_EXTERN VALUE rb_eFatal; RUBY_EXTERN VALUE rb_eArgError; RUBY_EXTERN VALUE rb_eEOFError; RUBY_EXTERN VALUE rb_eIndexError; RUBY_EXTERN VALUE rb_eRangeError; RUBY_EXTERN VALUE rb_eIOError; RUBY_EXTERN VALUE rb_eRuntimeError; RUBY_EXTERN VALUE rb_eSecurityError; RUBY_EXTERN VALUE rb_eSystemCallError; RUBY_EXTERN VALUE rb_eThreadError; RUBY_EXTERN VALUE rb_eTypeError; RUBY_EXTERN VALUE rb_eZeroDivError; RUBY_EXTERN VALUE rb_eNotImpError; RUBY_EXTERN VALUE rb_eNoMemError; RUBY_EXTERN VALUE rb_eNoMethodError; RUBY_EXTERN VALUE rb_eFloatDomainError; RUBY_EXTERN VALUE rb_eLocalJumpError; RUBY_EXTERN VALUE rb_eSysStackError; RUBY_EXTERN VALUE rb_eRegexpError; RUBY_EXTERN VALUE rb_eScriptError; RUBY_EXTERN VALUE rb_eNameError; RUBY_EXTERN VALUE rb_eSyntaxError; RUBY_EXTERN VALUE rb_eLoadError; RUBY_EXTERN VALUE rb_stdin, rb_stdout, rb_stderr; RUBY_EXTERN VALUE ruby_errinfo; static inline VALUE #if defined(HAVE_PROTOTYPES) rb_class_of(VALUE obj) #else rb_class_of(obj) VALUE obj; #endif { if (FIXNUM_P(obj)) return rb_cFixnum; if (obj == Qnil) return rb_cNilClass; if (obj == Qfalse) return rb_cFalseClass; if (obj == Qtrue) return rb_cTrueClass; if (SYMBOL_P(obj)) return rb_cSymbol; return RBASIC(obj)->klass; } static inline int #if defined(HAVE_PROTOTYPES) rb_type(VALUE obj) #else rb_type(obj) VALUE obj; #endif { if (FIXNUM_P(obj)) return T_FIXNUM; if (obj == Qnil) return T_NIL; if (obj == Qfalse) return T_FALSE; if (obj == Qtrue) return T_TRUE; if (obj == Qundef) return T_UNDEF; if (SYMBOL_P(obj)) return T_SYMBOL; return BUILTIN_TYPE(obj); } static inline int #if defined(HAVE_PROTOTYPES) rb_special_const_p(VALUE obj) #else rb_special_const_p(obj) VALUE obj; #endif { if (SPECIAL_CONST_P(obj)) return Qtrue; return Qfalse; } #include "missing.h" #include "intern.h" #if defined(EXTLIB) && defined(USE_DLN_A_OUT) /* hook for external modules */ static char *dln_libs_to_be_linked[] = { EXTLIB, 0 }; #endif #if defined(HAVE_LIBPTHREAD) #ifdef HAVE_PTHREAD_H #include #endif typedef pthread_t rb_nativethread_t; # define NATIVETHREAD_CURRENT() pthread_self() # define NATIVETHREAD_EQUAL(t1,t2) pthread_equal((t1),(t2)) # define HAVE_NATIVETHREAD # define NATIVETHREAD_KILL(th,sig) pthread_kill((th),(sig)) # define HAVE_NATIVETHREAD_KILL #elif defined(_WIN32) || defined(_WIN32_WCE) typedef DWORD rb_nativethread_t; # define NATIVETHREAD_CURRENT() GetCurrentThreadId() # define NATIVETHREAD_EQUAL(t1,t2) ((t1) == (t2)) # define HAVE_NATIVETHREAD #endif #ifdef HAVE_NATIVETHREAD int is_ruby_native_thread _((void)); #else #define is_ruby_native_thread() (1) #endif #ifdef HAVE_NATIVETHREAD_KILL void ruby_native_thread_kill _((int)); #endif #if defined(__cplusplus) #if 0 { /* satisfy cc-mode */ #endif } /* extern "C" { */ #endif #endif /* ifndef RUBY_H */ /********************************************************************** rubyio.h - $Author: shyouhei $ $Date: 2007-02-13 00:01:19 +0100 (Tue, 13 Feb 2007) $ created at: Fri Nov 12 16:47:09 JST 1993 Copyright (C) 1993-2003 Yukihiro Matsumoto **********************************************************************/ #ifndef RUBYIO_H #define RUBYIO_H #include #include #if defined(HAVE_STDIO_EXT_H) #include #endif typedef struct OpenFile { FILE *f; /* stdio ptr for read/write */ FILE *f2; /* additional ptr for rw pipes */ int mode; /* mode flags */ int pid; /* child's pid (for pipes) */ int lineno; /* number of lines read */ char *path; /* pathname for file */ void (*finalize) _((struct OpenFile*,int)); /* finalize proc */ } OpenFile; #define FMODE_READABLE 1 #define FMODE_WRITABLE 2 #define FMODE_READWRITE 3 #define FMODE_APPEND 64 #define FMODE_CREATE 128 #define FMODE_BINMODE 4 #define FMODE_SYNC 8 #define FMODE_WBUF 16 #define FMODE_RBUF 32 #define FMODE_WSPLIT 0x200 #define FMODE_WSPLIT_INITIALIZED 0x400 #define GetOpenFile(obj,fp) rb_io_check_closed((fp) = RFILE(rb_io_taint_check(obj))->fptr) #define MakeOpenFile(obj, fp) do {\ if (RFILE(obj)->fptr) {\ rb_io_close(obj);\ free(RFILE(obj)->fptr);\ RFILE(obj)->fptr = 0;\ }\ fp = 0;\ fp = RFILE(obj)->fptr = ALLOC(OpenFile);\ fp->f = fp->f2 = NULL;\ fp->mode = 0;\ fp->pid = 0;\ fp->lineno = 0;\ fp->path = NULL;\ fp->finalize = 0;\ } while (0) #define GetReadFile(fptr) ((fptr)->f) #define GetWriteFile(fptr) (((fptr)->f2) ? (fptr)->f2 : (fptr)->f) FILE *rb_fopen _((const char*, const char*)); FILE *rb_fdopen _((int, const char*)); int rb_getc _((FILE*)); long rb_io_fread _((char *, long, FILE *)); long rb_io_fwrite _((const char *, long, FILE *)); int rb_io_mode_flags _((const char*)); int rb_io_modenum_flags _((int)); void rb_io_check_writable _((OpenFile*)); void rb_io_check_readable _((OpenFile*)); void rb_io_fptr_finalize _((OpenFile*)); void rb_io_synchronized _((OpenFile*)); void rb_io_check_initialized _((OpenFile*)); void rb_io_check_closed _((OpenFile*)); int rb_io_wait_readable _((int)); int rb_io_wait_writable _((int)); void rb_io_set_nonblock(OpenFile *fptr); VALUE rb_io_taint_check _((VALUE)); NORETURN(void rb_eof_error _((void))); void rb_read_check _((FILE*)); int rb_read_pending _((FILE*)); #endif /********************************************************************** rubysig.h - $Author: shyouhei $ $Date: 2009-01-05 03:13:40 +0100 (Mon, 05 Jan 2009) $ created at: Wed Aug 16 01:15:38 JST 1995 Copyright (C) 1993-2003 Yukihiro Matsumoto **********************************************************************/ #ifndef SIG_H #define SIG_H #include #ifdef _WIN32 typedef LONG rb_atomic_t; # define ATOMIC_TEST(var) InterlockedExchange(&(var), 0) # define ATOMIC_SET(var, val) InterlockedExchange(&(var), (val)) # define ATOMIC_INC(var) InterlockedIncrement(&(var)) # define ATOMIC_DEC(var) InterlockedDecrement(&(var)) /* Windows doesn't allow interrupt while system calls */ # define TRAP_BEG do {\ int saved_errno = 0;\ rb_atomic_t trap_immediate = ATOMIC_SET(rb_trap_immediate, 1) # define TRAP_END\ ATOMIC_SET(rb_trap_immediate, trap_immediate);\ saved_errno = errno;\ CHECK_INTS;\ errno = saved_errno;\ } while (0) # define RUBY_CRITICAL(statements) do {\ rb_w32_enter_critical();\ statements;\ rb_w32_leave_critical();\ } while (0) #else typedef int rb_atomic_t; # define ATOMIC_TEST(var) ((var) ? ((var) = 0, 1) : 0) # define ATOMIC_SET(var, val) ((var) = (val)) # define ATOMIC_INC(var) (++(var)) # define ATOMIC_DEC(var) (--(var)) # define TRAP_BEG do {\ int saved_errno = 0;\ int trap_immediate = rb_trap_immediate;\ rb_trap_immediate = 1 # define TRAP_END rb_trap_immediate = trap_immediate;\ saved_errno = errno;\ CHECK_INTS;\ errno = saved_errno;\ } while (0) # define RUBY_CRITICAL(statements) do {\ int trap_immediate = rb_trap_immediate;\ rb_trap_immediate = 0;\ statements;\ rb_trap_immediate = trap_immediate;\ } while (0) #endif RUBY_EXTERN rb_atomic_t rb_trap_immediate; RUBY_EXTERN int rb_prohibit_interrupt; #define DEFER_INTS (rb_prohibit_interrupt++) #define ALLOW_INTS do {\ rb_prohibit_interrupt--;\ CHECK_INTS;\ } while (0) #define ENABLE_INTS (rb_prohibit_interrupt--) VALUE rb_with_disable_interrupt _((VALUE(*)(ANYARGS),VALUE)); RUBY_EXTERN rb_atomic_t rb_trap_pending; void rb_trap_restore_mask _((void)); RUBY_EXTERN int rb_thread_critical; RUBY_EXTERN int rb_thread_pending; void rb_thread_schedule _((void)); #if defined(HAVE_SETITIMER) || defined(_THREAD_SAFE) # define CHECK_INTS do {\ if (!(rb_prohibit_interrupt || rb_thread_critical)) {\ if (rb_thread_pending) rb_thread_schedule();\ if (rb_trap_pending) rb_trap_exec();\ }\ } while (0) #else /* pseudo preemptive thread switching */ RUBY_EXTERN int rb_thread_tick; #define THREAD_TICK 500 #define CHECK_INTS do {\ if (!(rb_prohibit_interrupt || rb_thread_critical)) {\ if (rb_thread_pending || rb_thread_tick-- <= 0) {\ rb_thread_tick = THREAD_TICK;\ rb_thread_schedule();\ }\ }\ if (rb_trap_pending) rb_trap_exec();\ } while (0) #endif #endif /* This is a public domain general purpose hash table package written by Peter Moore @ UCB. */ /* @(#) st.h 5.1 89/12/14 */ #ifndef ST_INCLUDED #define ST_INCLUDED #if SIZEOF_LONG == SIZEOF_VOIDP typedef unsigned long st_data_t; #elif SIZEOF_LONG_LONG == SIZEOF_VOIDP typedef unsigned LONG_LONG st_data_t; #else # error ---->> st.c requires sizeof(void*) == sizeof(long) to be compiled. <<--- - #endif #define ST_DATA_T_DEFINED typedef struct st_table st_table; struct st_hash_type { int (*compare)(); int (*hash)(); }; struct st_table { struct st_hash_type *type; int num_bins; int num_entries; struct st_table_entry **bins; }; #define st_is_member(table,key) st_lookup(table,key,(st_data_t *)0) enum st_retval {ST_CONTINUE, ST_STOP, ST_DELETE, ST_CHECK}; #ifndef _ # define _(args) args #endif #ifndef ANYARGS # ifdef __cplusplus # define ANYARGS ... # else # define ANYARGS # endif #endif st_table *st_init_table _((struct st_hash_type *)); st_table *st_init_table_with_size _((struct st_hash_type *, int)); st_table *st_init_numtable _((void)); st_table *st_init_numtable_with_size _((int)); st_table *st_init_strtable _((void)); st_table *st_init_strtable_with_size _((int)); int st_delete _((st_table *, st_data_t *, st_data_t *)); int st_delete_safe _((st_table *, st_data_t *, st_data_t *, st_data_t)); int st_insert _((st_table *, st_data_t, st_data_t)); int st_lookup _((st_table *, st_data_t, st_data_t *)); int st_foreach _((st_table *, int (*)(ANYARGS), st_data_t)); void st_add_direct _((st_table *, st_data_t, st_data_t)); void st_free_table _((st_table *)); void st_cleanup_safe _((st_table *, st_data_t)); st_table *st_copy _((st_table *)); #define ST_NUMCMP ((int (*)()) 0) #define ST_NUMHASH ((int (*)()) -2) #define st_numcmp ST_NUMCMP #define st_numhash ST_NUMHASH int st_strhash(); #endif /* ST_INCLUDED */ /********************************************************************** util.h - $Author: shyouhei $ $Date: 2007-02-13 00:01:19 +0100 (Tue, 13 Feb 2007) $ created at: Thu Mar 9 11:55:53 JST 1995 Copyright (C) 1993-2003 Yukihiro Matsumoto **********************************************************************/ #ifndef UTIL_H #define UTIL_H #ifndef _ #ifdef __cplusplus # ifndef HAVE_PROTOTYPES # define HAVE_PROTOTYPES 1 # endif # ifndef HAVE_STDARG_PROTOTYPES # define HAVE_STDARG_PROTOTYPES 1 # endif #endif #ifdef HAVE_PROTOTYPES # define _(args) args #else # define _(args) () #endif #ifdef HAVE_STDARG_PROTOTYPES # define __(args) args #else # define __(args) () #endif #endif #define scan_oct ruby_scan_oct unsigned long scan_oct _((const char*, int, int*)); #define scan_hex ruby_scan_hex unsigned long scan_hex _((const char*, int, int*)); #if defined(MSDOS) || defined(__CYGWIN32__) || defined(_WIN32) void ruby_add_suffix(); #endif void ruby_qsort _((void*, const int, const int, int (*)(), void*)); #define qsort(b,n,s,c,d) ruby_qsort(b,n,s,c,d) void ruby_setenv _((const char*, const char*)); void ruby_unsetenv _((const char*)); #undef setenv #undef unsetenv #define setenv(name,val) ruby_setenv(name,val) #define unsetenv(name,val) ruby_unsetenv(name); char *ruby_strdup _((const char*)); #undef strdup #define strdup(s) ruby_strdup(s) char *ruby_getcwd _((void)); #define my_getcwd() ruby_getcwd() double ruby_strtod _((const char*, char **)); #undef strtod #define strtod(s,e) ruby_strtod(s,e) #endif /* UTIL_H */ #define RUBY_VERSION "1.8.6" #define RUBY_RELEASE_DATE "2009-09-09" #define RUBY_VERSION_CODE 186 #define RUBY_RELEASE_CODE 20090909 #define RUBY_PATCHLEVEL 388 #define RUBY_VERSION_MAJOR 1 #define RUBY_VERSION_MINOR 8 #define RUBY_VERSION_TEENY 6 #define RUBY_RELEASE_YEAR 2009 #define RUBY_RELEASE_MONTH 9 #define RUBY_RELEASE_DAY 9 #ifdef RUBY_EXTERN RUBY_EXTERN const char ruby_version[]; RUBY_EXTERN const char ruby_release_date[]; RUBY_EXTERN const char ruby_platform[]; RUBY_EXTERN const int ruby_patchlevel; #endif