/**********************************************************************
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 (Fixnum
s, Symbol
s
* 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;i