diff options
author | gbrandl <devnull@localhost> | 2006-10-19 20:27:28 +0200 |
---|---|---|
committer | gbrandl <devnull@localhost> | 2006-10-19 20:27:28 +0200 |
commit | f4d019954468db777760d21f9243eca8b852c184 (patch) | |
tree | 328b8f8fac25338306b0e7b827686dcc7597df23 /tests/examplefiles/example.c | |
download | pygments-f4d019954468db777760d21f9243eca8b852c184.tar.gz |
[svn] Name change, round 4 (rename SVN root folder).
Diffstat (limited to 'tests/examplefiles/example.c')
-rw-r--r-- | tests/examplefiles/example.c | 29989 |
1 files changed, 29989 insertions, 0 deletions
diff --git a/tests/examplefiles/example.c b/tests/examplefiles/example.c new file mode 100644 index 00000000..bbd1a75c --- /dev/null +++ b/tests/examplefiles/example.c @@ -0,0 +1,29989 @@ +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include "codegen.h" +#include "symboltable.h" +#include "stringbuffer.h" + +extern void yyerror(char* msg); + +static stringBuffer* staticVariableBuffer; +static stringBuffer* classInitBuffer; +static stringBuffer* currentMethodBuffer; +static stringBuffer* finishedMethodsBuffer; +static stringBuffer* mainBuffer; + +static int currentMethodBufferIndex; +static int currentMethodStackSize; +static int currentMethodStackSizeMax; +static int currentMethodNumberOfLocals; + +static int classInitBufferIndex; +static int classInitStackSize; +static int classInitStackSizeMax; + +static int labelCounter = 0; +static int global = 1; + +char tempString[MAX_LENGTH_OF_COMMAND]; + +extern char* className; /* from minako-syntax.y */ + +/* forward declarations */ +static void increaseStackby(int stackdiff); +char convertType(int type); + +void codegenInit() { + staticVariableBuffer = newStringBuffer(); + classInitBuffer = newStringBuffer(); + currentMethodBuffer = 0; + finishedMethodsBuffer = newStringBuffer(); + mainBuffer = newStringBuffer(); + + stringBufferAppend(mainBuffer, "; ------- Header --------------------------------------------"); + sprintf(tempString, ".class public synchronized %s", className); + stringBufferAppend(mainBuffer, tempString); + stringBufferAppend(mainBuffer, ".super java/lang/Object"); + stringBufferAppend(mainBuffer, "; -----------------------------------------------------------"); + stringBufferAppend(mainBuffer, ""); + + stringBufferAppend(finishedMethodsBuffer, "; ------- Constructor ---------------------------------------"); + stringBufferAppend(finishedMethodsBuffer, ".method public <init>()V"); + stringBufferAppend(finishedMethodsBuffer, "\t.limit stack 1"); + stringBufferAppend(finishedMethodsBuffer, "\t.limit locals 1"); + stringBufferAppend(finishedMethodsBuffer, "\taload_0"); + stringBufferAppend(finishedMethodsBuffer, "\tinvokenonvirtual java/lang/Object/<init>()V"); + stringBufferAppend(finishedMethodsBuffer, "\treturn"); + stringBufferAppend(finishedMethodsBuffer, ".end method"); + stringBufferAppend(finishedMethodsBuffer, "; -----------------------------------------------------------"); + stringBufferAppend(finishedMethodsBuffer, ""); + + stringBufferAppend(staticVariableBuffer, "; ------- Class Variables -----------------------------------"); + + stringBufferAppend(classInitBuffer, "; ------- Class Initializer ---------------------------------"); + stringBufferAppend(classInitBuffer, ".method static <clinit>()V"); + classInitBufferIndex = classInitBuffer->numberOfNextElement; + stringBufferAppend(classInitBuffer, "\t.limit locals 0"); + +} + +void codegenAppendCommand(char* cmd, int stackdiff) { + char tempString[MAX_LENGTH_OF_COMMAND]; + sprintf(tempString, "\t%s", cmd); + if (global) stringBufferAppend(classInitBuffer, tempString); + else stringBufferAppend(currentMethodBuffer, tempString); + increaseStackby(stackdiff); +} + +void codegenInsertCommand(int address, char* cmd, int stackdiff) { + char tempString[MAX_LENGTH_OF_COMMAND]; + sprintf(tempString, "\t%s", cmd); + if (global) stringBufferInsert(classInitBuffer, address, tempString); + else stringBufferInsert(currentMethodBuffer, address, tempString); + increaseStackby(stackdiff); +} + +void codegenAppendLabel(int label) { + char tempString[MAX_LENGTH_OF_COMMAND]; + sprintf(tempString, "Label%d:", label); + if (global) stringBufferAppend(classInitBuffer, tempString); + else stringBufferAppend(currentMethodBuffer, tempString); +} + +void codegenAddVariable(char* name, int type) { + /*fprintf(stderr, "add variable %s(%d) global=%d ", name, convertType(type), global);*/ + if (global) { + if (type == TYPE_INT) sprintf(tempString, ".field static %s %c", name, 'I'); + else if (type == TYPE_FLOAT) sprintf(tempString, ".field static %s %c", name, 'F'); + else if (type == TYPE_BOOLEAN) sprintf(tempString, ".field static %s %c", name, 'Z'); + else yyerror("compiler-intern error in codegenAddGlobalVariable().\n"); + stringBufferAppend(staticVariableBuffer, tempString); + } + else { + currentMethodNumberOfLocals++; + } +} + +int codegenGetNextLabel() { + return labelCounter++; +} + +int codegenGetCurrentAddress() { + if (global) return classInitBuffer->numberOfNextElement; + else return currentMethodBuffer->numberOfNextElement; +} + +void codegenEnterFunction(symtabEntry* entry) { + currentMethodBuffer = newStringBuffer(); + currentMethodStackSize = 0; + currentMethodStackSizeMax = 0; + labelCounter = 1; + global = 0; + + if (strcmp(entry->name, "main") == 0) { + if (entry->idtype != TYPE_VOID) yyerror("main has to be void.\n"); + currentMethodNumberOfLocals = 1; + symtabInsert(strdup("#main-param#"), TYPE_VOID, CLASS_FUNC); + stringBufferAppend(currentMethodBuffer, "; ------- Methode ---- void main() --------------------------"); + stringBufferAppend(currentMethodBuffer, ".method public static main([Ljava/lang/String;)V"); + } + else { + int i; + currentMethodNumberOfLocals = entry->paramIndex; + stringBufferAppend(currentMethodBuffer, "; ------- Methode -------------------------------------------"); + sprintf(tempString, ".method public static %s(", entry->name); + for (i=entry->paramIndex-1; i>=0; i--) { + int type = entry->params[i]->idtype; + tempString[strlen(tempString)+1] = 0; + tempString[strlen(tempString)] = convertType(type); + } + tempString[strlen(tempString)+2] = 0; + tempString[strlen(tempString)+1] = convertType(entry->idtype); + tempString[strlen(tempString)] = ')'; + stringBufferAppend(currentMethodBuffer, tempString); + } + currentMethodBufferIndex = currentMethodBuffer->numberOfNextElement; +} + +void codegenLeaveFunction() { + global = 1; + sprintf(tempString, "\t.limit locals %d", currentMethodNumberOfLocals); + stringBufferInsert(currentMethodBuffer, currentMethodBufferIndex, tempString); + sprintf(tempString, "\t.limit stack %d", currentMethodStackSizeMax); + stringBufferInsert(currentMethodBuffer, currentMethodBufferIndex, tempString); + stringBufferAppend(currentMethodBuffer, "\treturn"); + stringBufferAppend(currentMethodBuffer, ".end method"); + stringBufferAppend(currentMethodBuffer, "; -----------------------------------------------------------"); + stringBufferAppend(currentMethodBuffer, ""); + + stringBufferConcatenate(finishedMethodsBuffer, currentMethodBuffer); +} + + + +void codegenFinishCode() { + stringBufferAppend(staticVariableBuffer, "; -----------------------------------------------------------"); + stringBufferAppend(staticVariableBuffer, ""); + + sprintf(tempString, "\t.limit stack %d", classInitStackSizeMax); + stringBufferInsert(classInitBuffer, classInitBufferIndex, tempString); + stringBufferAppend(classInitBuffer, "\treturn"); + stringBufferAppend(classInitBuffer, ".end method"); + stringBufferAppend(classInitBuffer, "; -----------------------------------------------------------"); + + stringBufferConcatenate(mainBuffer, staticVariableBuffer); + stringBufferConcatenate(mainBuffer, finishedMethodsBuffer); + stringBufferConcatenate(mainBuffer, classInitBuffer); + + stringBufferPrint(mainBuffer); +} + +static void increaseStackby(int stackdiff) { + if (global) { + classInitStackSize += stackdiff; + if (classInitStackSize > classInitStackSizeMax) classInitStackSizeMax = classInitStackSize; + } + else { + currentMethodStackSize += stackdiff; + if (currentMethodStackSize > currentMethodStackSizeMax) currentMethodStackSizeMax = currentMethodStackSize; + } +} + +char convertType(int type) { + switch(type) { + case TYPE_VOID: return 'V'; + case TYPE_INT: return 'I'; + case TYPE_FLOAT: return 'F'; + case TYPE_BOOLEAN: return 'Z'; + default: yyerror("compiler-intern error in convertType().\n"); + } + return 0; /* to avoid compiler-warning */ +} + + +//#include <stdlib.h> +//#include <stdio.h> + +int main() { + int a = 12, b = 44; + while (a != b) { + if (a > b) + a -= b; + else + b -= a; + } + printf("%d\n%d", a, 0X0);\ +} + + +/********************************************************************** + + array.c - + + $Author: murphy $ + $Date: 2005-11-05 04:33:55 +0100 (Sa, 05 Nov 2005) $ + 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" +#include "node.h" + +VALUE rb_cArray, rb_cValues; + +static ID id_cmp; + +#define ARY_DEFAULT_SIZE 16 + + +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 <code>true</code> 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; + + if (len < 0) { + rb_raise(rb_eArgError, "negative array size (or size too big)"); + } + if (len > 0 && len * sizeof(VALUE) <= len) { + rb_raise(rb_eArgError, "array size too big"); + } + if (len == 0) len++; + + ary = ary_alloc(klass); + 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 <stdarg.h> +#define va_init_list(a,b) va_start(a,b) +#else +#include <varargs.h> +#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; i<n; i++) { + RARRAY(ary)->ptr[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); + } + RARRAY(ary)->len = n; + + return ary; +} + +VALUE +#ifdef HAVE_STDARG_PROTOTYPES +rb_values_new(long n, ...) +#else +rb_values_new(n, va_alist) + long n; + va_dcl +#endif +{ + va_list ar; + VALUE val; + long i; + + val = ary_new(rb_cValues, n); + va_init_list(ar, n); + for (i=0; i<n; i++) { + RARRAY(val)->ptr[i] = va_arg(ar, VALUE); + } + va_end(ar); + RARRAY(val)->len = n; + + return val; +} + +VALUE +rb_values_new2(n, elts) + long n; + const VALUE *elts; +{ + VALUE val; + + val = ary_new(rb_cValues, n); + if (n > 0 && elts) { + RARRAY(val)->len = n; + MEMCPY(RARRAY(val)->ptr, elts, VALUE, n); + } + + return val; +} + +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; + } +} + +static VALUE +ary_shared_array(klass, ary) + VALUE klass, ary; +{ + VALUE val = ary_alloc(klass); + + ary_make_shared(ary); + RARRAY(val)->ptr = RARRAY(ary)->ptr; + RARRAY(val)->len = RARRAY(ary)->len; + RARRAY(val)->aux.shared = RARRAY(ary)->aux.shared; + FL_SET(val, ELTS_SHARED); + return val; +} + +VALUE +rb_values_from_ary(ary) + VALUE ary; +{ + return ary_shared_array(rb_cValues, ary); +} + +VALUE +rb_ary_from_values(val) + VALUE val; +{ + return ary_shared_array(rb_cArray, val); +} + +VALUE +rb_assoc_new(car, cdr) + VALUE car, cdr; +{ + return rb_values_new(2, car, cdr); +} + +static VALUE +to_ary(ary) + VALUE ary; +{ + return rb_convert_type(ary, T_ARRAY, "Array", "to_ary"); +} + +static VALUE +to_a(ary) + VALUE ary; +{ + return rb_convert_type(ary, T_ARRAY, "Array", "to_a"); +} + +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; + + 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 > 0 && len * (long)sizeof(VALUE) <= len) { + rb_raise(rb_eArgError, "array size too big"); + } + rb_ary_modify(ary); + 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; i<len; i++) { + rb_ary_store(ary, i, rb_yield(LONG2NUM(i))); + RARRAY(ary)->len = 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); + } + } + + 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; + } + new_capa += idx; + if (new_capa * (long)sizeof(VALUE) <= new_capa) { + rb_raise(rb_eArgError, "index too big"); + } + 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; +} + +static VALUE +ary_shared_first(argc, argv, ary) + int argc; + VALUE *argv; + VALUE ary; +{ + VALUE nv, result; + long n; + + rb_scan_args(argc, argv, "1", &nv); + n = NUM2LONG(nv); + if (n > RARRAY(ary)->len) { + n = RARRAY(ary)->len; + } + else if (n < 0) { + rb_raise(rb_eArgError, "negative array size"); + } + result = ary_shared_array(rb_cArray, ary); + RARRAY(result)->len = n; + return result; +} + +static VALUE +ary_shared_last(argc, argv, ary) + int argc; + VALUE *argv; + VALUE ary; +{ + VALUE result = ary_shared_first(argc, argv, ary); + + RARRAY(result)->ptr += RARRAY(ary)->len - RARRAY(result)->len; + return result; +} + +/* + * 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; +} + +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]; +} + +/* + * call-seq: + * array.pop -> obj or nil + * + * Removes the last element from <i>self</i> and returns it, or + * <code>nil</code> if the array is empty. + * + * a = [ "a", "b", "c", "d" ] + * a.pop #=> "d" + * a.pop(2) #=> ["b", "c"] + * a #=> ["a"] + */ + +static VALUE +rb_ary_pop_m(argc, argv, ary) + int argc; + VALUE *argv; + VALUE ary; +{ + VALUE result; + + if (argc == 0) { + return rb_ary_pop(ary); + } + + rb_ary_modify_check(ary); + + result = ary_shared_last(argc, argv, ary); + RARRAY(ary)->len -= RARRAY(result)->len; + return result; +} + +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]; + ary_make_shared(ary); + RARRAY(ary)->ptr++; /* shift ptr */ + RARRAY(ary)->len--; + + return top; +} + +/* + * call-seq: + * array.shift -> obj or nil + * + * Returns the first element of <i>self</i> and removes it (shifting all + * other elements down by one). Returns <code>nil</code> if the array + * is empty. + * + * args = [ "-m", "-q", "filename" ] + * args.shift #=> "-m" + * args #=> ["-q", "filename"] + * + * args = [ "-m", "-q", "filename" ] + * args.shift(2) #=> ["-m", "-q"] + * args #=> ["filename"] + */ + +static VALUE +rb_ary_shift_m(argc, argv, ary) + int argc; + VALUE *argv; + VALUE ary; +{ + VALUE result; + long n; + + if (argc == 0) { + return rb_ary_shift(ary); + } + + rb_ary_modify_check(ary); + + result = ary_shared_first(argc, argv, ary); + n = RARRAY(result)->len; + RARRAY(ary)->ptr += n; + RARRAY(ary)->len -= n; + + return result; +} + +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 <i>array</i>. + * 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 (beg + len > RARRAY(ary)->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) { + 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)); + } + /* 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 <code>Array#[]</code>. + * (<code>Array#at</code> is slightly faster than <code>Array#[]</code>, + * 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 of the array. If the array is empty, + * returns <code>nil</code>. + * + * a = [ "q", "r", "s", "t" ] + * a.first #=> "q" + * a.first(2) #=> ["q", "r"] + */ + +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 { + return ary_shared_first(argc, argv, ary); + } +} + +/* + * call-seq: + * array.last -> obj or nil + * array.last(n) -> an_array + * + * Returns the last element(s) of <i>self</i>. If the array is empty, + * the first form returns <code>nil</code>. + * + * a = [ "w", "x", "y", "z" ] + * a.last #=> "z" + * a.last(2) #=> ["y", "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 { + return ary_shared_last(argc, argv, ary); + } +} + +/* + * call-seq: + * array.fetch(index) -> obj + * array.fetch(index, default ) -> obj + * array.fetch(index) {|index| block } -> obj + * + * Tries to return the element at position <i>index</i>. If the index + * lies outside the array, the first form throws an + * <code>IndexError</code> exception, the second form returns + * <i>default</i>, and the third form returns the value of invoking + * the block, passing in the index. Negative values of <i>index</i> + * 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 + * array.index {|item| block} -> int or nil + * + * Returns the index of the first object in <i>self</i> such that is + * <code>==</code> to <i>obj</i>. If a block is given instead of an + * argument, returns first object for which <em>block</em> is true. + * Returns <code>nil</code> if no match is found. + * + * a = [ "a", "b", "c" ] + * a.index("b") #=> 1 + * a.index("z") #=> nil + * a.index{|x|x=="b"} #=> 1 + */ + +static VALUE +rb_ary_index(argc, argv, ary) + int argc; + VALUE *argv; + VALUE ary; +{ + VALUE val; + long i; + + if (rb_scan_args(argc, argv, "01", &val) == 0) { + for (i=0; i<RARRAY(ary)->len; i++) { + if (RTEST(rb_yield(RARRAY(ary)->ptr[i]))) { + return LONG2NUM(i); + } + } + } + else { + for (i=0; i<RARRAY(ary)->len; 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 <i>array</i> + * <code>==</code> to <i>obj</i>. If a block is given instead of an + * argument, returns first object for which <em>block</em> is + * true. Returns <code>nil</code> if no match is found. + * + * a = [ "a", "b", "b", "b", "c" ] + * a.rindex("b") #=> 3 + * a.rindex("z") #=> nil + * a.rindex{|x|x=="b"} #=> 3 + */ + +static VALUE +rb_ary_rindex(argc, argv, ary) + int argc; + VALUE *argv; + VALUE ary; +{ + VALUE val; + long i = RARRAY(ary)->len; + + if (rb_scan_args(argc, argv, "01", &val) == 0) { + while (i--) { + if (RTEST(rb_yield(RARRAY(ary)->ptr[i]))) + return LONG2NUM(i); + if (i > RARRAY(ary)->len) { + i = RARRAY(ary)->len; + } + } + } + else { + while (i--) { + if (rb_equal(RARRAY(ary)->ptr[i], val)) + return LONG2NUM(i); + if (i > RARRAY(ary)->len) { + i = RARRAY(ary)->len; + } + } + } + return Qnil; +} + +VALUE +rb_ary_to_ary(obj) + VALUE obj; +{ + if (TYPE(obj) == T_ARRAY) { + return obj; + } + if (rb_respond_to(obj, rb_intern("to_ary"))) { + return to_ary(obj); + } + return rb_ary_new3(1, obj); +} + +static void +rb_ary_splice(ary, beg, len, rpl) + VALUE ary; + long beg, len; + VALUE rpl; +{ + long rlen; + + if (len < 0) rb_raise(rb_eIndexError, "negative length (%ld)", len); + if (beg < 0) { + beg += RARRAY(ary)->len; + if (beg < 0) { + beg -= RARRAY(ary)->len; + rb_raise(rb_eIndexError, "index %ld out of array", beg); + } + } + if (beg + len > RARRAY(ary)->len) { + len = RARRAY(ary)->len - beg; + } + + if (rpl == Qundef) { + rlen = 0; + } + else { + rpl = rb_ary_to_ary(rpl); + rlen = RARRAY(rpl)->len; + } + rb_ary_modify(ary); + + if (beg >= RARRAY(ary)->len) { + 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. An +IndexError+ is raised if a negative index points + * past the beginning of the array. See also + * <code>Array#push</code>, and <code>Array#unshift</code>. + * + * 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", nil] + * a[1..-1] = [] #=> ["A"] + */ + +static VALUE +rb_ary_aset(argc, argv, ary) + int argc; + VALUE *argv; + VALUE ary; +{ + long offset, beg, len; + + if (argc == 3) { + 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 (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) { + rb_raise(rb_eArgError, "wrong number of arguments (at least 1)"); + } + pos = NUM2LONG(argv[0]); + if (pos == -1) { + pos = RARRAY(ary)->len; + } + else if (pos < 0) { + pos++; + } + + if (argc == 1) return ary; + rb_ary_splice(ary, pos, 0, rb_ary_new4(argc - 1, argv + 1)); + return ary; +} + +/* + * call-seq: + * array.each {|item| block } -> array + * + * Calls <i>block</i> once for each element in <i>self</i>, 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; i<RARRAY(ary)->len; i++) { + rb_yield(RARRAY(ary)->ptr[i]); + } + return ary; +} + +/* + * call-seq: + * array.each_index {|index| block } -> array + * + * Same as <code>Array#each</code>, 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; i<RARRAY(ary)->len; i++) { + rb_yield(LONG2NUM(i)); + } + return ary; +} + +/* + * call-seq: + * array.reverse_each {|item| block } + * + * Same as <code>Array#each</code>, but traverses <i>self</i> 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 <i>self</i>. 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 <code>true</code> if <i>self</i> 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 +recursive_join(ary, arg, recur) + VALUE ary; + VALUE *arg; + int recur; +{ + if (recur) { + return rb_str_new2("[...]"); + } + 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; i<RARRAY(ary)->len; 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; i<RARRAY(ary)->len; i++) { + tmp = RARRAY(ary)->ptr[i]; + switch (TYPE(tmp)) { + case T_STRING: + break; + case T_ARRAY: + { + VALUE args[2]; + + args[0] = tmp; + args[1] = sep; + tmp = rb_exec_recursive(recursive_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 <i>sep</i>. + * + * [ "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_<code>.join</code>. + * + * [ "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 VALUE +inspect_ary(ary, dummy, recur) + VALUE ary; + VALUE dummy; + int recur; +{ + int tainted = OBJ_TAINTED(ary); + long i; + VALUE s, str; + + if (recur) return rb_tainted_str_new2("[...]"); + str = rb_str_buf_new2("["); + for (i=0; i<RARRAY(ary)->len; 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 <i>array</i>. + */ + +static VALUE +rb_ary_inspect(ary) + VALUE ary; +{ + if (RARRAY(ary)->len == 0) return rb_str_new2("[]"); + return rb_exec_recursive(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 <i>self</i>'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_eRuntimeError, "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 && 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 <code><=></code> operator or using + * an optional code block. The block implements a comparison between + * <i>a</i> and <i>b</i>, returning -1, 0, or +1. See also + * <code>Enumerable#sort_by</code>. + * + * 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 <i>self</i>. Comparisons for + * the sort will be done using the <code><=></code> operator or using + * an optional code block. The block implements a comparison between + * <i>a</i> and <i>b</i>, returning -1, 0, or +1. See also + * <code>Enumerable#sort_by</code>. + * + * 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 <i>block</i> once for each element of <i>self</i>. Creates a + * new array containing the values returned by the block. + * See also <code>Enumerable#collect</code>. + * + * 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 <code>Enumerable#collect</code>. + * + * 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_get_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<argc; i++) { + if (FIXNUM_P(argv[i])) { + rb_ary_push(result, (*func)(obj, FIX2LONG(argv[i]))); + continue; + } + /* check if idx is Range */ + switch (rb_range_beg_len(argv[i], &beg, &len, olen, 0)) { + case Qfalse: + break; + case Qnil: + continue; + default: + for (j=0; j<len; j++) { + rb_ary_push(result, (*func)(obj, j+beg)); + } + continue; + } + rb_ary_push(result, (*func)(obj, NUM2LONG(argv[i]))); + } + return result; +} + +/* + * call-seq: + * array.values_at(selector,... ) -> an_array + * + * Returns an array containing the elements in + * _self_ corresponding to the given selector(s). The selectors + * may be either integer indices or ranges. + * See also <code>Array#select</code>. + * + * 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_get_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 <i>array</i>, + * returning an array containing those elements for which the block + * returns a true value (equivalent to <code>Enumerable#select</code>). + * + * 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 <i>self</i> that are equal to <i>obj</i>. If + * the item is not found, returns <code>nil</code>. If the optional + * code block is given, returns the result of <i>block</i> 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 <code>nil</code> if the index is out of range. See also + * <code>Array#slice!</code>. + * + * 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 + * <code>nil</code> 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, Qundef); /* 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 <code>Array#delete_if</code>, deleting elements from + * _self_ for which the block evaluates to true, but returns + * <code>nil</code> if no changes were made. Also see + * <code>Enumerable#reject</code>. + */ + +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 <i>self</i> for which <i>block</i> evaluates + * to <code>true</code>. + * + * 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 + * <i>self</i> with corresponding elements from each argument. This + * generates a sequence of <code>self.size</code> <em>n</em>-element + * arrays, where <em>n</em> is one more that the count of arguments. If + * the size of any argument is less than <code>enumObj.size</code>, + * <code>nil</code> 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; i<argc; i++) { + argv[i] = to_a(argv[i]); + } + if (rb_block_given_p()) { + for (i=0; i<RARRAY(ary)->len; i++) { + VALUE tmp = rb_ary_new2(argc+1); + + rb_ary_push(tmp, rb_ary_elt(ary, i)); + for (j=0; j<argc; j++) { + rb_ary_push(tmp, rb_ary_elt(argv[j], i)); + } + rb_yield(tmp); + } + return Qnil; + } + len = RARRAY(ary)->len; + result = rb_ary_new2(len); + for (i=0; i<len; i++) { + VALUE tmp = rb_ary_new2(argc+1); + + rb_ary_push(tmp, rb_ary_elt(ary, i)); + for (j=0; j<argc; j++) { + rb_ary_push(tmp, rb_ary_elt(argv[j], i)); + } + rb_ary_push(result, tmp); + } + return result; +} + +/* + * call-seq: + * array.transpose -> an_array + * + * Assumes that <i>self</i> 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; i<alen; i++) { + tmp = to_ary(rb_ary_elt(ary, i)); + if (elen < 0) { /* first element */ + elen = RARRAY(tmp)->len; + result = rb_ary_new2(elen); + for (j=0; j<elen; j++) { + rb_ary_store(result, j, rb_ary_new2(alen)); + } + } + else if (elen != RARRAY(tmp)->len) { + rb_raise(rb_eIndexError, "element size differs (%d should be %d)", + RARRAY(tmp)->len, elen); + } + for (j=0; j<elen; j++) { + rb_ary_store(rb_ary_elt(result, j), i, rb_ary_elt(tmp, j)); + } + } + return result; +} + +/* + * call-seq: + * array.replace(other_array) -> array + * + * Replaces the contents of <i>self</i> with the contents of + * <i>other_array</i>, 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 <i>self</i> (which + * may be the entire array) to <i>obj</i>. A <i>start</i> of + * <code>nil</code> is equivalent to zero. A <i>length</i> of + * <code>nil</code> is equivalent to <i>self.length</i>. 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, end, len; + 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); + 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; + } + if (beg > RARRAY(ary)->len) { + 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<end; i++) { + v = rb_yield(LONG2NUM(i)); + if (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 (LONG_MAX/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; i<len; i+=RARRAY(ary)->len) { + 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 <code>Array#rassoc</code>. + * + * 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 + * <em>key</em> with the second element of each contained array using + * <code>==</code>. Returns the first contained array that matches. See + * also <code>Array#assoc</code>. + * + * 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; +} + +/* + * 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; +{ + long i; + + 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; + for (i=0; i<RARRAY(ary1)->len; i++) { + if (!rb_equal(rb_ary_elt(ary1, i), rb_ary_elt(ary2, i))) + return Qfalse; + } + return Qtrue; +} + +/* + * call-seq: + * array.eql?(other) -> true or false + * + * Returns <code>true</code> 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; +{ + long i; + + if (ary1 == ary2) return Qtrue; + if (TYPE(ary2) != T_ARRAY) return Qfalse; + if (RARRAY(ary1)->len != RARRAY(ary2)->len) return Qfalse; + for (i=0; i<RARRAY(ary1)->len; i++) { + if (!rb_eql(rb_ary_elt(ary1, i), rb_ary_elt(ary2, i))) + return Qfalse; + } + return Qtrue; +} + +static VALUE +recursive_hash(ary, dummy, recur) + VALUE ary, dummy; + int recur; +{ + long i, h; + VALUE n; + + if (recur) { + return LONG2FIX(0); + } + h = RARRAY(ary)->len; + for (i=0; i<RARRAY(ary)->len; 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 <code>eql?</code>). + */ + +static VALUE +rb_ary_hash(ary) + VALUE ary; +{ + return rb_exec_recursive(recursive_hash, ary, 0); +} + +/* + * call-seq: + * array.include?(obj) -> true or false + * + * Returns <code>true</code> if the given object is present in + * <i>self</i> (that is, if any object <code>==</code> <i>anObject</i>), + * <code>false</code> 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; i<RARRAY(ary)->len; i++) { + if (rb_equal(RARRAY(ary)->ptr[i], item)) { + return Qtrue; + } + } + return Qfalse; +} + + +/* + * call-seq: + * array <=> 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 <code>Array#<=></code> 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 i, len; + + ary2 = to_ary(ary2); + len = RARRAY(ary1)->len; + if (len > RARRAY(ary2)->len) { + len = RARRAY(ary2)->len; + } + for (i=0; i<len; i++) { + VALUE v = rb_funcall(rb_ary_elt(ary1, i), id_cmp, 1, rb_ary_elt(ary2, i)); + if (v != INT2FIX(0)) { + 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; i<RARRAY(ary1)->len; i++) { + rb_hash_aset(hash, RARRAY(ary1)->ptr[i], Qtrue); + } + if (ary2) { + for (i=0; i<RARRAY(ary2)->len; 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, hash; + long i; + + hash = ary_make_hash(to_ary(ary2), 0); + ary3 = rb_ary_new(); + + for (i=0; i<RARRAY(ary1)->len; 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; i<RARRAY(ary1)->len; 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; i<RARRAY(ary1)->len; 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; i<RARRAY(ary2)->len; 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 <code>nil</code> 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; i<RARRAY(ary)->len; 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 <i>self</i>. + * + * 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-<code>nil</code> 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 <code>nil</code> if no modifications were made (i.e., + * <i>array</i> 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 (i<RARRAY(ary)->len) { + 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_m, -1); + rb_define_method(rb_cArray, "shift", rb_ary_shift_m, -1); + 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, "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("<=>"); + + rb_cValues = rb_define_class("Values", rb_cArray); +} +/********************************************************************** + ascii.c - Oniguruma (regular expression library) +**********************************************************************/ +/*- + * Copyright (c) 2002-2004 K.Kosako <sndgk393 AT ybb DOT ne DOT jp> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "regenc.h" + +static int +ascii_is_code_ctype(OnigCodePoint code, unsigned int ctype) +{ + if (code < 128) + return ONIGENC_IS_ASCII_CODE_CTYPE(code, ctype); + else + return FALSE; +} + +OnigEncodingType OnigEncodingASCII = { + onigenc_single_byte_mbc_enc_len, + "US-ASCII", /* name */ + 1, /* max byte length */ + 1, /* min byte length */ + ONIGENC_AMBIGUOUS_MATCH_ASCII_CASE, + { + (OnigCodePoint )'\\' /* esc */ + , (OnigCodePoint )ONIG_INEFFECTIVE_META_CHAR /* anychar '.' */ + , (OnigCodePoint )ONIG_INEFFECTIVE_META_CHAR /* anytime '*' */ + , (OnigCodePoint )ONIG_INEFFECTIVE_META_CHAR /* zero or one time '?' */ + , (OnigCodePoint )ONIG_INEFFECTIVE_META_CHAR /* one or more time '+' */ + , (OnigCodePoint )ONIG_INEFFECTIVE_META_CHAR /* anychar anytime */ + }, + onigenc_is_mbc_newline_0x0a, + onigenc_single_byte_mbc_to_code, + onigenc_single_byte_code_to_mbclen, + onigenc_single_byte_code_to_mbc, + onigenc_ascii_mbc_to_normalize, + onigenc_ascii_is_mbc_ambiguous, + onigenc_ascii_get_all_pair_ambig_codes, + onigenc_nothing_get_all_comp_ambig_codes, + ascii_is_code_ctype, + onigenc_not_support_get_ctype_code_range, + onigenc_single_byte_left_adjust_char_head, + onigenc_always_true_is_allowed_reverse_match +}; +/********************************************************************** + + bignum.c - + + $Author: murphy $ + $Date: 2005-11-05 04:33:55 +0100 (Sa, 05 Nov 2005) $ + created at: Fri Jun 10 00:48:55 JST 1994 + + Copyright (C) 1993-2003 Yukihiro Matsumoto + +**********************************************************************/ + +#include "ruby.h" + +#include <math.h> +#include <ctype.h> +#ifdef HAVE_IEEEFP_H +#include <ieeefp.h> +#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 || (RBIGNUM(x)->len == 1 && BDIGITS(x)[0] == 0)) + +static VALUE +bignew_1(klass, len, sign) + VALUE klass; + long len; + char sign; +{ + NEWOBJ(big, struct RBignum); + OBJSETUP(big, klass, T_BIGNUM); + big->sign = sign; + 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; +} + +static void +get2comp(x, carry) /* get 2's complement */ + VALUE x; + int carry; +{ + long i = RBIGNUM(x)->len; + BDIGIT *ds = BDIGITS(x); + BDIGIT_DBL num; + + 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 (!carry) return; + if ((ds[RBIGNUM(x)->len-1] & (1<<(BITSPERDIG-1))) == 0) { + REALLOC_N(RBIGNUM(x)->digits, BDIGIT, ++RBIGNUM(x)->len); + ds = BDIGITS(x); + ds[RBIGNUM(x)->len-1] = ~0; + } +} + +void +rb_big_2comp(x) /* get 2's complement */ + VALUE x; +{ + get2comp(x, Qtrue); +} + +static VALUE +bignorm(x) + VALUE x; +{ + if (!FIXNUM_P(x)) { + long len = RBIGNUM(x)->len; + BDIGIT *ds = BDIGITS(x); + + while (len-- && !ds[len]) ; + RBIGNUM(x)->len = ++len; + + 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; +} + +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) { + len = SIZEOF_LONG/SIZEOF_BDIGITS; + } + 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; + + 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'); + --str; + } + 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++) { + if (c == '_') { + if (badcheck) { + if (nondigit) goto bad; + nondigit = c; + } + continue; + } + else if (!ISASCII(c)) { + break; + } + else if (isdigit(c)) { + c -= '0'; + } + else if (islower(c)) { + c -= 'a' - 10; + } + else if (isupper(c)) { + c -= 'A' - 10; + } + else { + break; + } + if (c >= base) break; + nondigit = 0; + i = 0; + num = c; + for (;;) { + while (i<blen) { + num += (BDIGIT_DBL)zds[i]*base; + zds[i++] = BIGLO(num); + num = BIGDN(num); + } + if (num) { + blen++; + continue; + } + break; + } + } + if (badcheck) { + str--; + if (s+1 < str && str[-1] == '_') goto bad; + while (*str && ISSPACE(*str)) str++; + if (*str) { + bad: + rb_invalid_str(s, "Integer"); + } + } + + return bignorm(z); +} + +VALUE +rb_str_to_inum(str, base, badcheck) + VALUE str; + int base; + int badcheck; +{ + char *s; + long len; + + StringValue(str); + if (badcheck) { + s = StringValueCStr(str); + } + else { + s = RSTRING(str)->ptr; + } + 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_big2str(x, base) + VALUE x; + int base; +{ + volatile VALUE t; + BDIGIT *ds; + long i, j, hbase; + VALUE ss; + char *s, c; + + if (FIXNUM_P(x)) { + return rb_fix2str(x, base); + } + i = RBIGNUM(x)->len; + if (BIGZEROP(x)) { + return rb_str_new2("0"); + } + j = SIZEOF_BDIGITS*CHAR_BIT*i; + switch (base) { + case 2: break; + case 3: + j = j * 647L / 1024; + break; + case 4: case 5: case 6: case 7: + j /= 2; + break; + case 8: case 9: + j /= 3; + break; + case 10: case 11: case 12: case 13: case 14: case 15: + j = j * 241L / 800; + 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 /= 4; + break; + case 32: case 33: case 34: case 35: case 36: + j /= 5; + break; + default: + rb_raise(rb_eArgError, "illegal radix %d", base); + break; + } + j += 2; + + hbase = base * base; +#if SIZEOF_BDIGITS > 2 + hbase *= hbase; +#endif + + t = rb_big_clone(x); + ds = BDIGITS(t); + ss = rb_str_new(0, j); + s = RSTRING(ss)->ptr; + + s[0] = RBIGNUM(x)->sign ? '+' : '-'; + while (i && j) { + long k = i; + BDIGIT_DBL num = 0; + + while (k--) { + num = BIGUP(num) + ds[k]; + ds[k] = (BDIGIT)(num / hbase); + num %= hbase; + } + if (ds[i-1] == 0) i--; + k = SIZEOF_BDIGITS; + while (k--) { + c = (char)(num % base); + s[--j] = ruby_digitmap[(int)c]; + num /= base; + if (i == 0 && num == 0) break; + } + } + while (s[j] == '0') j++; + RSTRING(ss)->len -= RBIGNUM(x)->sign?j:j-1; + memmove(RBIGNUM(x)->sign?s:s+1, s+j, RSTRING(ss)->len); + s[RSTRING(ss)->len] = '\0'; + + return ss; +} + +/* + * call-seq: + * big.to_s(base=10) => string + * + * Returns a string containing the representation of <i>big</i> radix + * <i>base</i> (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, check) + VALUE x; + char *type; + int check; +{ + long len = RBIGNUM(x)->len; + BDIGIT_DBL num; + BDIGIT *ds; + + if (len > SIZEOF_LONG/SIZEOF_BDIGITS) { + if (check) + rb_raise(rb_eRangeError, "bignum too big to convert into `%s'", type); + len = SIZEOF_LONG/SIZEOF_BDIGITS; + } + 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", Qfalse); + if (!RBIGNUM(x)->sign) { + return -num; + } + return num; +} + +unsigned long +rb_big2ulong(x) + VALUE x; +{ + unsigned long num = big2ulong(x, "unsigned long", Qtrue); + + 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", Qtrue); + + 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 <i>big</i> to a <code>Float</code>. If <i>big</i> doesn't + * fit in a <code>Float</code>, 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 <i>big</i> is + * less than, equal to, or greater than <i>numeric</i>. This is the + * basis for the tests in <code>Comparable</code>. + * + */ + +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: + return rb_dbl_cmp(rb_big2dbl(x), RFLOAT(y)->value); + + 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 <code>true</code> only if <i>obj</i> has the same value + * as <i>big</i>. Contrast this with <code>Bignum#eql?</code>, which + * requires <i>obj</i> to be a <code>Bignum</code>. + * + * 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; + b = rb_big2dbl(x); + if (isnan(a) || isnan(b)) return Qfalse; + 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 <code>true</code> only if <i>obj</i> is a + * <code>Bignum</code> with the same value as <i>big</i>. Contrast this + * with <code>Bignum#==</code>, 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 = RBIGNUM(x)->len; + BDIGIT *ds = BDIGITS(z); + + if (!RBIGNUM(x)->sign) get2comp(z, Qtrue); + while (i--) ds[i] = ~ds[i]; + if (RBIGNUM(x)->sign) get2comp(z, Qfalse); + RBIGNUM(z)->sign = !RBIGNUM(z)->sign; + + 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)?1: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; + char 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); + } +} + +/* + * call-seq: + * big * other => Numeric + * + * Multiplies big and other, returning the result. + */ + +VALUE +rb_big_mul(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 bignorm(z); +} + +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) { + t2 += (BDIGIT_DBL)yds[j]<<dd; + tds[j++] = BIGLO(t2); + t2 = BIGDN(t2); + } + yds = tds; + j = 0; + t2 = 0; + while (j<nx) { + t2 += (BDIGIT_DBL)xds[j]<<dd; + zds[j++] = BIGLO(t2); + t2 = BIGDN(t2); + } + zds[j] = (BDIGIT)t2; + } + else { + zds[nx] = 0; + j = nx; + while (j--) zds[j] = xds[j]; + } + + j = nx==ny?nx+1:nx; + do { + if (zds[j] == yds[ny-1]) q = BIGRAD-1; + else q = (BDIGIT)((BIGUP(zds[j]) + zds[j-1])/yds[ny-1]); + if (q) { + i = 0; num = 0; t2 = 0; + do { /* multiply and subtract */ + BDIGIT_DBL ee; + t2 += (BDIGIT_DBL)yds[i] * q; + ee = num - BIGLO(t2); + num = (BDIGIT_DBL)zds[j - ny + i] + ee; + if (ee) zds[j - ny + i] = BIGLO(num); + num = BIGDN(num); + t2 = BIGDN(t2); + } while (++i < ny); + num += zds[j - ny + i] - t2;/* borrow from high digit; don't update */ + while (num) { /* "add back" required */ + i = 0; num = 0; q--; + do { + BDIGIT_DBL ee = num + yds[i]; + num = (BDIGIT_DBL)zds[j - ny + i] + ee; + if (ee) zds[j - ny + i] = BIGLO(num); + num = BIGDN(num); + } while (++i < ny); + num--; + } + } + zds[j] = q; + } 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 <i>big</i> by <i>numeric</i>. + * + * -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); +} + +/* + * call-seq: + * big.divmod(numeric) => array + * + * See <code>Numeric#divmod</code>. + * + */ +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 <i>big</i> by + * <i>numeric</i>. + * + * -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; + + for (;;) { + yy -= 1; + if (yy == 0) break; + while (yy % 2 == 0) { + yy /= 2; + x = rb_big_mul(x, x); + } + z = rb_big_mul(z, x); + } + 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, Qtrue); + } + if (!RBIGNUM(x)->sign) { + x = rb_big_clone(x); + get2comp(x, Qtrue); + } + 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; i<l1; i++) { + zds[i] = ds1[i] & ds2[i]; + } + for (; i<l2; i++) { + zds[i] = sign?0:ds2[i]; + } + if (!RBIGNUM(z)->sign) get2comp(z, Qfalse); + 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, Qtrue); + } + if (!RBIGNUM(x)->sign) { + x = rb_big_clone(x); + get2comp(x, Qtrue); + } + 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; i<l1; i++) { + zds[i] = ds1[i] | ds2[i]; + } + for (; i<l2; i++) { + zds[i] = sign?ds2[i]:(BIGRAD-1); + } + if (!RBIGNUM(z)->sign) get2comp(z, Qfalse); + + 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, Qtrue); + } + if (!RBIGNUM(x)->sign) { + x = rb_big_clone(x); + get2comp(x, Qtrue); + } + 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; i<l1; i++) { + zds[i] = ds1[i] ^ ds2[i]; + } + for (; i<l2; i++) { + zds[i] = sign?ds2[i]:~ds2[i]; + } + if (!RBIGNUM(z)->sign) get2comp(z, Qfalse); + + return bignorm(z); +} + +static VALUE rb_big_rshift _((VALUE,VALUE)); + +/* + * call-seq: + * big << numeric => integer + * + * Shifts big left _numeric_ positions (right if _numeric_ is negative). + */ + +VALUE +rb_big_lshift(x, y) + VALUE x, y; +{ + BDIGIT *xds, *zds; + int shift = NUM2INT(y); + int s1 = shift/BITSPERDIG; + int s2 = shift%BITSPERDIG; + VALUE z; + BDIGIT_DBL num = 0; + long len, i; + + if (shift < 0) return rb_big_rshift(x, INT2FIX(-shift)); + len = RBIGNUM(x)->len; + z = bignew(len+s1+1, RBIGNUM(x)->sign); + zds = BDIGITS(z); + for (i=0; i<s1; i++) { + *zds++ = 0; + } + xds = BDIGITS(x); + for (i=0; i<len; i++) { + num = num | (BDIGIT_DBL)*xds++<<s2; + *zds++ = BIGLO(num); + num = BIGDN(num); + } + *zds = BIGLO(num); + return bignorm(z); +} + +/* + * call-seq: + * big >> numeric => integer + * + * Shifts big right _numeric_ positions (left if _numeric_ is negative). + */ + +static VALUE +rb_big_rshift(x, y) + VALUE x, y; +{ + BDIGIT *xds, *zds; + int shift = NUM2INT(y); + long s1 = shift/BITSPERDIG; + long s2 = shift%BITSPERDIG; + VALUE z; + BDIGIT_DBL num = 0; + long i, j; + + if (shift < 0) return rb_big_lshift(x, INT2FIX(-shift)); + + if (s1 > RBIGNUM(x)->len) { + if (RBIGNUM(x)->sign) + return INT2FIX(0); + else + return INT2FIX(-1); + } + if (!RBIGNUM(x)->sign) { + x = rb_big_clone(x); + get2comp(x, Qtrue); + } + xds = BDIGITS(x); + i = RBIGNUM(x)->len; j = i - s1; + 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, Qfalse); + } + return bignorm(z); +} + +/* + * call-seq: + * big[n] -> 0, 1 + * + * Bit Reference---Returns the <em>n</em>th bit in the (assumed) binary + * representation of <i>big</i>, where <i>big</i>[0] is the least + * significant bit. + * + * a = 9**15 + * 50.downto(0) do |n| + * print a[n] + * end + * + * <em>produces:</em> + * + * 000101110110100000111000011110010100111100010111001 + * + */ + +static VALUE +rb_big_aref(x, y) + VALUE x, y; +{ + BDIGIT *xds; + int shift; + long s1, s2; + + if (TYPE(y) == T_BIGNUM) { + if (!RBIGNUM(y)->sign || RBIGNUM(x)->sign) + return INT2FIX(0); + return INT2FIX(1); + } + shift = NUM2INT(y); + if (shift < 0) return INT2FIX(0); + s1 = shift/BITSPERDIG; + s2 = shift%BITSPERDIG; + + if (!RBIGNUM(x)->sign) { + if (s1 >= RBIGNUM(x)->len) return INT2FIX(1); + x = rb_big_clone(x); + get2comp(x, Qtrue); + } + else { + if (s1 >= RBIGNUM(x)->len) return INT2FIX(0); + } + xds = BDIGITS(x); + if (xds[s1] & (1<<s2)) + return INT2FIX(1); + return INT2FIX(0); +} + +/* + * call-seq: + * big.hash => 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<len; i++) { + key ^= *digits++; + } + return LONG2FIX(key); +} + +/* + * MISSING: documentation + */ + +static VALUE +rb_big_coerce(x, y) + VALUE x, y; +{ + if (FIXNUM_P(y)) { + return rb_assoc_new(rb_int2big(FIX2LONG(y)), x); + } + else { + rb_raise(rb_eTypeError, "can't coerce %s to Bignum", + rb_obj_classname(y)); + } + /* not reached */ + return Qnil; +} + +/* + * call-seq: + * big.abs -> aBignum + * + * Returns the absolute value of <i>big</i>. + * + * -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 + * <i>big</i>. + * + * (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 <code>[]</code>, 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: murphy $ + $Date: 2005-11-05 04:33:55 +0100 (Sa, 05 Nov 2005) $ + 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 <ctype.h> + +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; +} + +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 singleton class"); + } +} + +VALUE +rb_class_new(super) + VALUE super; +{ + Check_Type(super, T_CLASS); + rb_check_inheritable(super); + if (super == rb_cClass) { + rb_raise(rb_eTypeError, "can't make subclass of Class"); + } + return rb_class_boot(super); +} + +static int +clone_method(mid, body, tbl) + ID mid; + NODE *body; + st_table *tbl; +{ + st_insert(tbl, mid, (st_data_t)NEW_METHOD(body->nd_body, 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 = rb_singleton_class_clone(orig); + } + 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) { + RCLASS(clone)->m_tbl = st_init_numtable(); + st_foreach(RCLASS(orig)->m_tbl, clone_method, + (st_data_t)RCLASS(clone)->m_tbl); + } + + 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"); + } + 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); + } + clone->m_tbl = st_init_numtable(); + st_foreach(RCLASS(klass)->m_tbl, clone_method, + (st_data_t)clone->m_tbl); + 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; +{ + if (BUILTIN_TYPE(obj) == T_CLASS && FL_TEST(obj, FL_SINGLETON)) { + return RBASIC(obj)->klass = rb_cClass; + } + else { + VALUE metasuper; + VALUE klass = rb_class_boot(super); + + FL_SET(klass, FL_SINGLETON); + RBASIC(obj)->klass = klass; + rb_singleton_class_attached(klass, obj); + + 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; +} + +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 (NIL_P(module)) return; + if (klass == module) return; + + 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 <i>mod</i>. + * + * 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 <code>true</code> if <i>module</i> is included in + * <i>mod</i> or one of <i>mod</i>'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 <i>mod</i> (including + * <i>mod</i> 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 <code>false</code>, the + * instance methods in <i>mod</i> are returned, otherwise the methods + * in <i>mod</i> and <i>mod</i>'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 + * <i>mod</i>. If the optional parameter is not <code>false</code>, 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 + * <i>mod</i>. If the optional parameter is not <code>false</code>, 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 <i>mod</i>. + * If the optional parameter is not <code>false</code>, 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 <i>obj</i>. + * If the optional <i>all</i> parameter is true, the list will include + * methods in modules included in <i>obj</i>. + * + * 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; +{ + rb_add_method(klass, rb_intern(name), NEW_CFUNC(func, argc), NOEX_PUBLIC); +} + +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 <stdarg.h> +#define va_init_list(a,b) va_start(a,b) +#else +#include <varargs.h> +#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<n; i++) { + var = va_arg(vargs, VALUE*); + if (var) *var = argv[i]; + } + p++; + } + else { + goto error; + } + + if (ISDIGIT(*p)) { + n = i + *p - '0'; + for (; i<n; i++) { + var = va_arg(vargs, VALUE*); + if (argc > 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: murphy $ + $Date: 2005-11-05 04:33:55 +0100 (Sa, 05 Nov 2005) $ + 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); +} + +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 <code><=></code> + * 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 <code><=></code> + * 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 (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 <code><=></code> + * 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 (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 <code><=></code> + * 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 (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 <code><=></code> + * 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 (rb_cmpint(c, x, y) <= 0) return Qtrue; + return Qfalse; +} + +/* + * call-seq: + * obj.between?(min, max) => true or false + * + * Returns <code>false</code> if <i>obj</i> <code><=></code> + * <i>min</i> is less than zero or if <i>anObject</i> <code><=></code> + * <i>max</i> is greater than zero, <code>true</code> 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 <code>Comparable</code> mixin is used by classes whose objects + * may be ordered. The class must define the <code><=></code> 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. <code>Comparable</code> uses + * <code><=></code> to implement the conventional comparison operators + * (<code><</code>, <code><=</code>, <code>==</code>, <code>>=</code>, + * and <code>></code>) and the method <code>between?</code>. + * + * 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: murphy $ + $Date: 2005-11-05 04:33:55 +0100 (Sa, 05 Nov 2005) $ + 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 <sys/types.h> +#include <sys/stat.h> + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#if defined HAVE_DIRENT_H && !defined _WIN32 +# include <dirent.h> +# define NAMLEN(dirent) strlen((dirent)->d_name) +#elif defined HAVE_DIRECT_H && !defined _WIN32 +# include <direct.h> +# 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 <sys/ndir.h> +# endif +# if HAVE_SYS_DIR_H +# include <sys/dir.h> +# endif +# if HAVE_NDIR_H +# include <ndir.h> +# endif +# ifdef _WIN32 +# include "win32/dir.h" +# endif +#endif + +#include <errno.h> + +#ifndef HAVE_STDLIB_H +char *getenv(); +#endif + +#ifndef HAVE_STRING_H +char *strchr _((char*,char)); +#endif + +#include <ctype.h> + +#include "util.h" + +#if !defined HAVE_LSTAT && !defined lstat +#define lstat stat +#endif + +#define FNM_NOESCAPE 0x01 +#define FNM_PATHNAME 0x02 +#define FNM_DOTMATCH 0x04 +#define FNM_CASEFOLD 0x08 + +#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(p) + 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(p1, p2, nocase) + 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) { + 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; + + FilePathValue(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, <code>open</code> is a synonym for + * <code>Dir::new</code>. If a block is present, it is passed + * <i>aDir</i> as a parameter. The directory is closed at the end of + * the block, and <code>Dir::open</code> 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"); +} + +#define GetDIR(obj, dirp) do {\ + 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(s)->ptr, 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 <em>dir</em>'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 <em>dir</em> and returns it as a string. + * Returns <code>nil</code> 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}" } + * + * <em>produces:</em> + * + * 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); + 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 <em>dir</em>. See also + * <code>Dir#seek</code>. + * + * 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 <em>dir</em>. <i>integer</i> + * must be a value returned by <code>Dir#tell</code>. + * + * d = Dir.new("testdir") #=> #<Dir:0x401b3c40> + * d.read #=> "." + * i = d.tell #=> 12 + * d.read #=> ".." + * d.seek(i) #=> #<Dir:0x401b3c40> + * 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 <code>Dir#seek</code>, but returns the position + * parameter. + * + * d = Dir.new("testdir") #=> #<Dir:0x401b3c40> + * 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 <em>dir</em> to the first entry. + * + * d = Dir.new("testdir") + * d.read #=> "." + * d.rewind #=> #<Dir:0x401b3fb0> + * d.read #=> "." + */ +static VALUE +dir_rewind(dir) + VALUE dir; +{ + struct dir_data *dirp; + + GetDIR(dir, dirp); + rewinddir(dirp->dir); + return dir; +} + +/* + * call-seq: + * dir.close => nil + * + * Closes the directory stream. Any further attempts to access + * <em>dir</em> will raise an <code>IOError</code>. + * + * 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 <code>HOME</code>, or + * <code>LOGDIR</code>. <code>SystemCallError</code> (probably + * <code>Errno::ENOENT</code>) 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 <code>chdir</code> is the value of the + * block. <code>chdir</code> blocks can be nested, but in a + * multi-threaded program an error will be raised if a thread attempts + * to open a <code>chdir</code> 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 + * + * <em>produces:</em> + * + * /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) { + FilePathValue(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; + + rb_secure(2); + FilePathValue(*dir); + 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 <code>chroot(2)</code> 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 <i>string</i>, with permissions + * specified by the optional parameter <i>anInteger</i>. The + * permissions may be modified by the value of + * <code>File::umask</code>, and are ignored on NT. Raises a + * <code>SystemCallError</code> if the directory cannot be created. See + * also the discussion of permissions in the class documentation for + * <code>File</code>. + * + */ +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 + * <code>SystemCallError</code> 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); +} + +/* System call with warning */ +static int +do_stat(path, pst) + const char *path; + struct stat *pst; +{ + int ret = stat(path, pst); + if (ret < 0 && errno != ENOENT) + rb_sys_warning(path); + + return ret; +} + +static int +do_lstat(path, pst) + const char *path; + struct stat *pst; +{ + int ret = lstat(path, pst); + if (ret < 0 && errno != ENOENT) + rb_sys_warning(path); + + return ret; +} + +static DIR * +do_opendir(path) + const char *path; +{ + DIR *dirp = opendir(path); + if (dirp == NULL && errno != ENOENT && errno != ENOTDIR) + rb_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); + + 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; + } + + p = Next(p-1); + } + + return 0; +} + +/* Find separator in globbing pattern. */ +static char * +find_dirsep(s, flags) + 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 baskclashes */ +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 struct glob_pattern * +glob_make_pattern(p, flags) + const char *p; + int flags; +{ + struct glob_pattern *list, *tmp, **tail = &list; + int dirsep = 0; /* pattern is terminated with '/' */ + + while (*p) { + tmp = ALLOC(struct glob_pattern); + 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 = ALLOC_N(char, m-p+1); + 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 = ALLOC(struct glob_pattern); + tmp->type = dirsep ? MATCH_DIR : MATCH_ALL; + tmp->str = 0; + *tail = tmp; + tmp->next = 0; + + return list; +} + +static void +glob_free_pattern(list) + struct glob_pattern *list; +{ + while (list) { + struct glob_pattern *tmp = list; + list = list->next; + if (tmp->str) + free(tmp->str); + free(tmp); + } +} + +static VALUE +join_path(path, dirsep, name) + VALUE path; + int dirsep; + const char *name; +{ + long len = RSTRING(path)->len; + VALUE buf = rb_str_new(0, RSTRING(path)->len+strlen(name)+(dirsep?1:0)); + + memcpy(RSTRING(buf)->ptr, RSTRING(path)->ptr, len); + if (dirsep) { + strcpy(RSTRING(buf)->ptr+len, "/"); + len++; + } + strcpy(RSTRING(buf)->ptr+len, name); + return buf; +} + +enum answer { YES, NO, UNKNOWN }; + +#ifndef S_ISDIR +# define S_ISDIR(m) ((m & S_IFMT) == S_IFDIR) +#endif + +#ifndef S_ISLNK +# ifndef S_IFLNK +# define S_ISLNK(m) (0) +# else +# define S_ISLNK(m) ((m & S_IFMT) == S_IFLNK) +# endif +#endif + +struct glob_args { + void (*func) _((VALUE, VALUE)); + VALUE 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; + VALUE path = args->c; + + OBJ_TAINT(path); + (*args->func)(path, args->v); + return Qnil; +} + +static int +glob_call_func(func, path, arg) + void (*func) _((VALUE, VALUE)); + VALUE path; + VALUE arg; +{ + int status; + struct glob_args args; + + args.func = func; + args.c = path; + args.v = arg; + + rb_protect(glob_func_caller, (VALUE)&args, &status); + return status; +} + +static int +glob_helper(path, dirsep, exist, isdir, beg, end, flags, func, arg) + VALUE 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; + void (*func) _((VALUE, VALUE)); + 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; + } + } + + if (RSTRING(path)->len > 0) { + if (match_all && exist == UNKNOWN) { + if (do_lstat(RSTRING(path)->ptr, &st) == 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(RSTRING(path)->ptr, &st) == 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) { + status = glob_call_func(func, join_path(path, dirsep, ""), arg); + if (status) return status; + } + } + + if (exist == NO || isdir == NO) return 0; + + if (magical || recursive) { + struct dirent *dp; + DIR *dirp = do_opendir(RSTRING(path)->len > 0 ? RSTRING(path)->ptr : "."); + if (dirp == NULL) return 0; + + for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) { + VALUE buf = join_path(path, dirsep, dp->d_name); + + enum answer new_isdir = UNKNOWN; + if (recursive && strcmp(dp->d_name, ".") != 0 && strcmp(dp->d_name, "..") != 0 + && fnmatch("*", dp->d_name, flags) == 0) { +#ifndef _WIN32 + if (do_lstat(RSTRING(buf)->ptr, &st) == 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 = ALLOC_N(struct glob_pattern *, (end - beg) * 2); + + 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(new_beg); + if (status) break; + } + + closedir(dirp); + } + else if (plain) { + struct glob_pattern **copy_beg, **copy_end, **cur2; + + copy_beg = copy_end = ALLOC_N(struct glob_pattern *, end - beg); + for (cur = beg; cur < end; ++cur) + *copy_end++ = (*cur)->type == PLAIN ? *cur : 0; + + for (cur = copy_beg; cur < copy_end; ++cur) { + if (*cur) { + VALUE buf; + char *name; + name = ALLOC_N(char, strlen((*cur)->str) + 1); + strcpy(name, (*cur)->str); + if (escape) remove_backslashes(name); + + new_beg = new_end = ALLOC_N(struct glob_pattern *, end - beg); + *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); + status = glob_helper(buf, 1, UNKNOWN, UNKNOWN, new_beg, new_end, flags, func, arg); + free(new_beg); + if (status) break; + } + } + + free(copy_beg); + } + + return status; +} + +static int +rb_glob2(path, offset, flags, func, arg) + VALUE path; + long offset; + int flags; + void (*func) _((VALUE, VALUE)); + VALUE arg; +{ + struct glob_pattern *list; + const char *root, *start; + VALUE buf; + int n; + int status; + + if (flags & FNM_CASEFOLD) { + rb_warn("Dir.glob() ignores File::FNM_CASEFOLD"); + } + + start = root = StringValuePtr(path) + offset; +#if defined DOSISH + flags |= FNM_CASEFOLD; + root = rb_path_skip_prefix(root); +#else + flags &= ~FNM_CASEFOLD; +#endif + + if (root && *root == '/') root++; + + n = root - start; + buf = rb_str_new(start, n); + + list = glob_make_pattern(root, flags); + status = glob_helper(buf, 0, UNKNOWN, UNKNOWN, &list, &list + 1, flags, func, arg); + glob_free_pattern(list); + + return status; +} + +struct rb_glob_args { + void (*func) _((const char*, VALUE)); + VALUE arg; +}; + +static VALUE +rb_glob_caller(path, a) + VALUE path, a; +{ + struct rb_glob_args *args = (struct rb_glob_args *)a; + (*args->func)(RSTRING(path)->ptr, args->arg); + return Qnil; +} + +void +rb_glob(path, func, arg) + const char *path; + void (*func) _((const char*, VALUE)); + VALUE arg; +{ + struct rb_glob_args args; + int status; + + args.func = func; + args.arg = arg; + status = rb_glob2(rb_str_new2(path), 0, 0, rb_glob_caller, &args); + + if (status) rb_jump_tag(status); +} + +static void +push_pattern(path, ary) + VALUE path, ary; +{ + rb_ary_push(ary, path); +} + +static int +push_glob(VALUE ary, VALUE s, long offset, int flags); + +static int +push_glob(ary, str, offset, flags) + VALUE ary; + VALUE str; + long offset; + int flags; +{ + const int escape = !(flags & FNM_NOESCAPE); + + const char *p = RSTRING(str)->ptr + offset; + 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) { + VALUE buffer = rb_str_new(0, strlen(s)); + char *buf; + long shift; + + buf = RSTRING(buffer)->ptr; + 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 = push_glob(ary, buffer, offset, flags); + if (status) break; + } + } + else if (!lbrace && !rbrace) { + status = rb_glob2(str, offset, flags, push_pattern, ary); + } + + return status; +} + +static VALUE +rb_push_glob(str, flags) /* '\0' is delimiter */ + VALUE str; + int flags; +{ + long offset = 0; + VALUE ary; + + FilePathValue(str); + + ary = rb_ary_new(); + + while (offset < RSTRING(str)->len) { + int status = push_glob(ary, str, offset, flags); + char *p, *pend; + if (status) rb_jump_tag(status); + p = RSTRING(str)->ptr + offset; + p += strlen(p) + 1; + pend = RSTRING(str)->ptr + RSTRING(str)->len; + while (p < pend && !*p) + p++; + offset = p - RSTRING(str)->ptr; + } + + if (rb_block_given_p()) { + rb_ary_each(ary); + return Qnil; + } + return ary; +} + +/* + * call-seq: + * Dir[ string ] => array + * + * Equivalent to calling + * <em>dir</em>.<code>glob(</code><i>string,</i><code>0)</code>. + * + */ +static VALUE +dir_s_aref(obj, str) + VALUE obj, str; +{ + return rb_push_glob(str, 0); +} + +/* + * call-seq: + * Dir.glob( string, [flags] ) => array + * Dir.glob( string, [flags] ) {| filename | block } => nil + * + * Returns the filenames found by expanding the pattern given in + * <i>string</i>, either as an <i>array</i> or as parameters to the + * block. Note that this pattern is not a regexp (it's closer to a + * shell glob). See <code>File::fnmatch</code> for the meaning of + * the <i>flags</i> parameter. Note that case sensitivity + * depends on your system (so <code>File::FNM_CASEFOLD</code> is ignored) + * + * <code>*</code>:: Matches any file. Can be restricted by + * other values in the glob. <code>*</code> + * will match all files; <code>c*</code> will + * match all files beginning with + * <code>c</code>; <code>*c</code> will match + * all files ending with <code>c</code>; and + * <code>*c*</code> will match all files that + * have <code>c</code> in them (including at + * the beginning or end). Equivalent to + * <code>/ .* /x</code> in regexp. + * <code>**</code>:: Matches directories recursively. + * <code>?</code>:: Matches any one character. Equivalent to + * <code>/.{1}/</code> in regexp. + * <code>[set]</code>:: Matches any one character in +set+. + * Behaves exactly like character sets in + * Regexp, including set negation + * (<code>[^a-z]</code>). + * <code>{p,q}</code>:: Matches either literal <code>p</code> or + * literal <code>q</code>. Matching literals + * may be more than one character in length. + * More than two literals may be specified. + * Equivalent to pattern alternation in + * regexp. + * <code>\</code>:: 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; + int flags; + + if (rb_scan_args(argc, argv, "11", &str, &rflags) == 2) + flags = NUM2INT(rflags); + else + flags = 0; + + return rb_push_glob(str, flags); +} + +static VALUE +dir_open_dir(path) + VALUE path; +{ + struct dir_data *dp; + 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}" } + * + * <em>produces:</em> + * + * 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 <code>SystemCallError</code> 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 <i>path</i> matches against <i>pattern</i> The + * pattern is not a regular expression; instead it follows rules + * similar to shell filename globbing. It may contain the following + * metacharacters: + * + * <code>*</code>:: Matches any file. Can be restricted by + * other values in the glob. <code>*</code> + * will match all files; <code>c*</code> will + * match all files beginning with + * <code>c</code>; <code>*c</code> will match + * all files ending with <code>c</code>; and + * <code>*c*</code> will match all files that + * have <code>c</code> in them (including at + * the beginning or end). Equivalent to + * <code>/ .* /x</code> in regexp. + * <code>**</code>:: Matches directories recursively or files + * expansively. + * <code>?</code>:: Matches any one character. Equivalent to + * <code>/.{1}/</code> in regexp. + * <code>[set]</code>:: Matches any one character in +set+. + * Behaves exactly like character sets in + * Regexp, including set negation + * (<code>[^a-z]</code>). + * <code>\</code>:: Escapes the next metacharacter. + * + * <i>flags</i> is a bitwise OR of the <code>FNM_xxx</code> + * parameters. The same glob pattern and flags are used by + * <code>Dir::glob</code>. + * + * 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 = File.join("**", "*.rb") + * 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 + * + * File.fnmatch('* IGNORE /*', 'dave/.profile', File::FNM_PATHNAME) #=> false + * File.fnmatch('* IGNORE /*', 'dave/.profile', File::FNM_PATHNAME | File::FNM_DOTMATCH) #=> true + * + * File.fnmatch('** IGNORE /foo', 'a/b/c/foo', File::FNM_PATHNAME) #=> true + * File.fnmatch('** IGNORE /foo', '/a/b/c/foo', File::FNM_PATHNAME) #=> true + * File.fnmatch('** IGNORE /foo', 'c:/a/b/c/foo', File::FNM_PATHNAME) #=> true + * File.fnmatch('** IGNORE /foo', 'a/.b/c/foo', File::FNM_PATHNAME) #=> false + * File.fnmatch('** IGNORE /foo', '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 <code>Dir</code> are directory streams representing + * directories in the underlying file system. They provide a variety of + * ways to list directories and their contents. See also + * <code>File</code>. + * + * The directory used in these examples contains the two regular files + * (<code>config.h</code> and <code>main.rb</code>), the parent + * directory (<code>..</code>), and the directory itself + * (<code>.</code>). + */ +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,"inspect", dir_inspect, 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)); +} +/********************************************************************** + + dln.c - + + $Author: murphy $ + $Date: 2005-11-05 04:33:55 +0100 (Sa, 05 Nov 2005) $ + 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 <stdlib.h> +#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 + +#ifdef _AIX +#pragma alloca +#endif + +#if defined(HAVE_ALLOCA_H) +#include <alloca.h> +#endif + +#ifdef HAVE_STRING_H +# include <string.h> +#else +# include <strings.h> +#endif + +#ifndef xmalloc +void *xmalloc(); +void *xcalloc(); +void *xrealloc(); +#endif + +#include <stdio.h> +#if defined(_WIN32) || defined(__VMS) +#include "missing/file.h" +#endif +#include <sys/types.h> +#include <sys/stat.h> + +#ifndef S_ISDIR +# define S_ISDIR(m) ((m & S_IFMT) == S_IFDIR) +#endif + +#ifdef HAVE_SYS_PARAM_H +# include <sys/param.h> +#endif +#ifndef MAXPATHLEN +# define MAXPATHLEN 1024 +#endif + +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif + +#ifndef _WIN32 +char *getenv(); +#endif + +#if defined(__VMS) +#pragma builtins +#include <dlfcn.h> +#endif + +#ifdef __MACOS__ +# include <TextUtils.h> +# include <CodeFragments.h> +# include <Aliases.h> +# include "macruby_private.h" +#endif + +#ifdef __BEOS__ +# include <image.h> +#endif + +int eaccess(); + +#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 <errno.h> + +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 <ar.h> +#include <a.out.h> +#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; sym<end; sym++) { + char *name = sym->n_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') { + char *p = alloca(strlen(lib) + 4); + sprintf(p, "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 +# include <dlfcn.h> +#endif + +#ifdef __hpux +#include <errno.h> +#include "dl.h" +#endif + +#if defined(_AIX) +#include <ctype.h> /* for isdigit() */ +#include <errno.h> /* for global errno */ +#include <sys/ldr.h> +#endif + +#ifdef NeXT +#if NS_TARGET_MAJOR < 4 +#include <mach-o/rld.h> +#else +#include <mach-o/dyld.h> +#ifndef NSLINKMODULE_OPTION_BINDNOW +#define NSLINKMODULE_OPTION_BINDNOW 1 +#endif +#endif +#else +#ifdef __APPLE__ +#include <mach-o/dyld.h> +#endif +#endif + +#if defined _WIN32 && !defined __CYGWIN__ +#include <windows.h> +#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<LOAD_ERRTAB_LEN; j++) { + if (nerr == load_errtab[i].errnum && load_errtab[i].errstr) + ERRBUF_APPEND(load_errtab[i].errstr); + } + while (isdigit(*message[i])) message[i]++; + ERRBUF_APPEND(message[i]); + ERRBUF_APPEND("\n"); + } + errbuf[strlen(errbuf)-1] = '\0'; /* trim off last newline */ + rb_loaderror(errbuf); + return; +} +#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 + { + void *handle, (*init_fct)(); + char *fname, *p1, *p2; + + fname = (char *)__alloca(strlen(file)+1); + strcpy(fname,file); + if (p1 = strrchr(fname,'/')) + fname = p1 + 1; + if (p2 = strrchr(fname,'.')) + *p2 = '\0'; + + if ((handle = (void*)dlopen(fname, 0)) == NULL) { + error = dln_strerror(); + goto failed; + } + + if ((init_fct = (void (*)())dlsym(handle, buf)) == NULL) { + error = DLN_ERROR(); + dlclose(handle); + goto failed; + } + /* Call the init code */ + (*init_fct)(); + return handle; + } +#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 +} + +#if defined(__CYGWIN32__) +const char * +conv_to_posix_path(win32, posix, len) + char *win32; + char *posix; + int len; +{ + char *first = win32; + char *p = win32; + char *dst = posix; + + for (p = win32; *p; p++) + if (*p == ';') { + *p = 0; + cygwin32_conv_to_posix_path(first, posix); + posix += strlen(posix); + *posix++ = ':'; + first = p + 1; + *p = ';'; + } + if (len < strlen(first)) + fprintf(stderr, "PATH length too long: %s\n", first); + else + cygwin32_conv_to_posix_path(first, posix); + return dst; +} +#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); + +#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 +#if defined(DOSISH) + if (exe_flag) { + static const char *extension[] = { +#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 + (char *) NULL + }; + int j; + + for (j = 0; extension[j]; 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 + } + } +#endif /* MSDOS or _WIN32 or __human68k__ or __EMX__ */ + + next: + /* if not, and no other alternatives, life is bleak */ + if (*ep == '\0') { + return NULL; + } + + /* otherwise try the next component in the search path */ + } +} +#define NO_DLN_LOAD 1 +#include "dln.c" +void +Init_ext() +{ +} +/********************************************************************** + + enum.c - + + $Author: murphy $ + $Date: 2005-11-05 04:33:55 +0100 (Sa, 05 Nov 2005) $ + 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 <i>enum</i> for which + * <code>Pattern === element</code>. If the optional <em>block</em> 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; + NODE *memo; +{ + if (RTEST(rb_yield(i))) { + memo->u2.value = Qtrue; + memo->u1.value = 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 <i>enum</i> to <em>block</em>. Returns the + * first for which <em>block</em> is not <code>false</code>. If no + * object matches, calls <i>ifnone</i> and returns its result when it + * is specified, or returns <code>nil</code> + * + * (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; +{ + NODE *memo = rb_node_newnode(NODE_MEMO, Qnil, Qfalse, 0); + VALUE if_none; + + rb_scan_args(argc, argv, "01", &if_none); + rb_iterate(rb_each, obj, find_i, (VALUE)memo); + if (memo->u2.value) { + return memo->u1.value; + } + 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 <i>enum</i> for which + * <em>block</em> is not <code>false</code> (see also + * <code>Enumerable#reject</code>). + * + * (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 <i>enum</i> for which + * <em>block</em> is false (see also <code>Enumerable#find_all</code>). + * + * (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 <em>block</em> once + * for every element in <i>enum</i>. + * + * (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 <i>enum</i>. + * + * (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; + NODE *memo; +{ + if (memo->u2.value) { + memo->u2.value = Qfalse; + memo->u1.value = i; + } + else { + memo->u1.value = rb_yield_values(2, memo->u1.value, i); + } + return Qnil; +} + +/* + * call-seq: + * enum.inject(initial) {| memo, obj | block } => obj + * enum.inject {| memo, obj | block } => obj + * + * Combines the elements of <i>enum</i> by applying the block to an + * accumulator value (<i>memo</i>) and each element in turn. At each + * step, <i>memo</i> is set to the value returned by the block. The + * first form lets you supply an initial value for <i>memo</i>. 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; +{ + NODE *memo; + VALUE n; + + if (rb_scan_args(argc, argv, "01", &n) == 1) { + memo = rb_node_newnode(NODE_MEMO, n, Qfalse, 0); + } + else { + memo = rb_node_newnode(NODE_MEMO, Qnil, Qtrue, 0); + } + rb_iterate(rb_each, obj, inject_i, (VALUE)memo); + n = memo->u1.value; + return n; +} + +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 + * <i>enum</i> 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 <i>enum</i> sorted, + * either according to their own <code><=></code> method, or by using + * the results of the supplied block. The block should return -1, 0, or + * +1 depending on the comparison between <i>a</i> and <i>b</i>. As of + * Ruby 1.8, the method <code>Enumerable#sort_by</code> 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 <i>enum</i> using a set of keys generated by mapping the + * values in <i>enum</i> through the given block. + * + * %w{ apple pear fig }.sort_by {|word| word.length} + #=> ["fig", "pear", "apple"] + * + * The current implementation of <code>sort_by</code> generates an + * array of tuples containing the original collection element and the + * mapped value. This makes <code>sort_by</code> 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 + * + * <em>produces:</em> + * + * 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 <code>sort</code> 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 <code>File</code> + * objects during every comparison. A slightly better technique is to + * use the <code>Kernel#test</code> 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 <code>Time</code> 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 <code>sort_by</code> 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; i<RARRAY(ary)->len; 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; + NODE *memo; +{ + if (!RTEST(rb_yield(i))) { + memo->u1.value = Qfalse; + rb_iter_break(); + } + return Qnil; +} + +static VALUE +all_i(i, memo) + VALUE i; + NODE *memo; +{ + if (!RTEST(i)) { + memo->u1.value = 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 <code>true</code> if the block never returns + * <code>false</code> or <code>nil</code>. If the block is not given, + * Ruby adds an implicit block of <code>{|obj| obj}</code> (that is + * <code>all?</code> will return <code>true</code> only if none of the + * collection members are <code>false</code> or <code>nil</code>.) + * + * %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; + NODE *memo = rb_node_newnode(NODE_MEMO, Qnil, 0, 0); + + memo->u1.value = Qtrue; + rb_iterate(rb_each, obj, rb_block_given_p() ? all_iter_i : all_i, (VALUE)memo); + result = memo->u1.value; + return result; +} + +static VALUE +any_iter_i(i, memo) + VALUE i; + NODE *memo; +{ + if (RTEST(rb_yield(i))) { + memo->u1.value = Qtrue; + rb_iter_break(); + } + return Qnil; +} + +static VALUE +any_i(i, memo) + VALUE i; + NODE *memo; +{ + if (RTEST(i)) { + memo->u1.value = 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 <code>true</code> if the block ever returns a value other + * that <code>false</code> or <code>nil</code>. If the block is not + * given, Ruby adds an implicit block of <code>{|obj| obj}</code> (that + * is <code>any?</code> will return <code>true</code> if at least one + * of the collection members is not <code>false</code> or + * <code>nil</code>. + * + * %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; + NODE *memo = rb_node_newnode(NODE_MEMO, Qnil, 0, 0); + + memo->u1.value = Qfalse; + rb_iterate(rb_each, obj, rb_block_given_p() ? any_iter_i : any_i, (VALUE)memo); + result = memo->u1.value; + return result; +} + +static VALUE +min_i(i, memo) + VALUE i; + NODE *memo; +{ + VALUE cmp; + + if (NIL_P(memo->u1.value)) { + memo->u1.value = i; + } + else { + cmp = rb_funcall(i, id_cmp, 1, memo->u1.value); + if (rb_cmpint(cmp, i, memo->u1.value) < 0) { + memo->u1.value = i; + } + } + return Qnil; +} + +static VALUE +min_ii(i, memo) + VALUE i; + NODE *memo; +{ + VALUE cmp; + + if (NIL_P(memo->u1.value)) { + memo->u1.value = i; + } + else { + cmp = rb_yield_values(2, i, memo->u1.value); + if (rb_cmpint(cmp, i, memo->u1.value) < 0) { + memo->u1.value = i; + } + } + return Qnil; +} + + +/* + * call-seq: + * enum.min => obj + * enum.min {| a,b | block } => obj + * + * Returns the object in <i>enum</i> with the minimum value. The + * first form assumes all objects implement <code>Comparable</code>; + * the second uses the block to return <em>a <=> b</em>. + * + * 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; + NODE *memo = rb_node_newnode(NODE_MEMO, Qnil, 0, 0); + + rb_iterate(rb_each, obj, rb_block_given_p() ? min_ii : min_i, (VALUE)memo); + result = memo->u1.value; + return result; +} + +static VALUE +max_i(i, memo) + VALUE i; + NODE *memo; +{ + VALUE cmp; + + if (NIL_P(memo->u1.value)) { + memo->u1.value = i; + } + else { + cmp = rb_funcall(i, id_cmp, 1, memo->u1.value); + if (rb_cmpint(cmp, i, memo->u1.value) > 0) { + memo->u1.value = i; + } + } + return Qnil; +} + +static VALUE +max_ii(i, memo) + VALUE i; + NODE *memo; +{ + VALUE cmp; + + if (NIL_P(memo->u1.value)) { + memo->u1.value = i; + } + else { + cmp = rb_yield_values(2, i, memo->u1.value); + if (rb_cmpint(cmp, i, memo->u1.value) > 0) { + memo->u1.value = 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 <code>Comparable</code>; + * the second uses the block to return <em>a <=> b</em>. + * + * 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; + NODE *memo = rb_node_newnode(NODE_MEMO, Qnil, 0, 0); + + rb_iterate(rb_each, obj, rb_block_given_p() ? max_ii : max_i, (VALUE)memo); + result = memo->u1.value; + return result; +} + +static VALUE +min_by_i(i, memo) + VALUE i; + NODE *memo; +{ + VALUE v; + + v = rb_yield(i); + if (NIL_P(memo->u1.value)) { + memo->u1.value = v; + memo->u2.value = i; + } + else if (rb_cmpint(rb_funcall(v, id_cmp, 1, memo->u1.value), v, memo->u1.value) < 0) { + memo->u1.value = v; + memo->u2.value = i; + } + return Qnil; +} + +/* + * call-seq: + * enum.min_by {| obj| block } => obj + * + * Returns the object in <i>enum</i> that gives the minimum + * value from the given block. + * + * a = %w(albatross dog horse) + * a.min_by {|x| x.length } #=> "dog" + */ + +static VALUE +enum_min_by(obj) + VALUE obj; +{ + VALUE result; + NODE *memo = rb_node_newnode(NODE_MEMO, Qnil, 0, 0); + + rb_iterate(rb_each, obj, min_by_i, (VALUE)memo); + result = memo->u2.value; + return result; +} + +static VALUE +max_by_i(i, memo) + VALUE i; + NODE *memo; +{ + VALUE v; + + v = rb_yield(i); + if (NIL_P(memo->u1.value)) { + memo->u1.value = v; + memo->u2.value = i; + } + else if (rb_cmpint(rb_funcall(v, id_cmp, 1, memo->u1.value), v, memo->u1.value) > 0) { + memo->u1.value = v; + memo->u2.value = i; + } + return Qnil; +} + +/* + * call-seq: + * enum.max_by {| obj| block } => obj + * + * Returns the object in <i>enum</i> that gives the maximum + * value from the given block. + * + * a = %w(albatross dog horse) + * a.max_by {|x| x.length } #=> "albatross" + */ + +static VALUE +enum_max_by(obj) + VALUE obj; +{ + VALUE result; + NODE *memo = rb_node_newnode(NODE_MEMO, Qnil, 0, 0); + + rb_iterate(rb_each, obj, max_by_i, (VALUE)memo); + result = memo->u2.value; + return result; +} + +static VALUE +member_i(item, memo) + VALUE item; + NODE *memo; +{ + if (rb_equal(item, memo->u1.value)) { + memo->u2.value = Qtrue; + rb_iter_break(); + } + return Qnil; +} + +/* + * call-seq: + * enum.include?(obj) => true or false + * enum.member?(obj) => true or false + * + * Returns <code>true</code> if any member of <i>enum</i> equals + * <i>obj</i>. Equality is tested using <code>==</code>. + * + * IO.constants.include? "SEEK_SET" #=> true + * IO.constants.include? "SEEK_NO_FURTHER" #=> false + * + */ + +static VALUE +enum_member(obj, val) + VALUE obj, val; +{ + VALUE result; + NODE *memo = rb_node_newnode(NODE_MEMO, val, Qfalse, 0); + + rb_iterate(rb_each, obj, member_i, (VALUE)memo); + result = memo->u2.value; + return result; +} + +static VALUE +each_with_index_i(val, memo) + VALUE val; + NODE *memo; +{ + rb_yield_values(2, val, INT2FIX(memo->u3.cnt)); + memo->u3.cnt++; + return Qnil; +} + +/* + * call-seq: + * enum.each_with_index {|obj, i| block } -> enum + * + * Calls <em>block</em> with two arguments, the item and its index, for + * each item in <i>enum</i>. + * + * 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; +{ + NODE *memo = rb_node_newnode(NODE_MEMO, 0, 0, 0); + + rb_iterate(rb_each, obj, each_with_index_i, (VALUE)memo); + return obj; +} + +static VALUE +zip_i(val, memo) + VALUE val; + NODE *memo; +{ + VALUE result = memo->u1.value; + VALUE args = memo->u2.value; + int idx = memo->u3.cnt++; + VALUE tmp; + int i; + + tmp = rb_ary_new2(RARRAY(args)->len + 1); + rb_ary_store(tmp, 0, val); + for (i=0; i<RARRAY(args)->len; 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 + * <i>enum</i> with corresponding elements from each argument. This + * generates a sequence of <code>enum#size</code> <em>n</em>-element + * arrays, where <em>n</em> is one more that the count of arguments. If + * the size of any argument is less than <code>enum#size</code>, + * <code>nil</code> 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; + NODE *memo; + + for (i=0; i<argc; i++) { + argv[i] = rb_convert_type(argv[i], T_ARRAY, "Array", "to_a"); + } + result = rb_block_given_p() ? Qnil : rb_ary_new(); + memo = rb_node_newnode(NODE_MEMO, result, rb_ary_new4(argc, argv), 0); + rb_iterate(rb_each, obj, zip_i, (VALUE)memo); + + return result; +} + +/* + * The <code>Enumerable</code> mixin provides collection classes with + * several traversal and searching methods, and with the ability to + * sort. The class must provide a method <code>each</code>, which + * yields successive members of the collection. If + * <code>Enumerable#max</code>, <code>#min</code>, or + * <code>#sort</code> is used, the objects in the collection must also + * implement a meaningful <code><=></code> 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,"min_by", enum_min_by, 0); + rb_define_method(rb_mEnumerable,"max_by", enum_max_by, 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: murphy $ + $Date: 2005-11-05 04:33:55 +0100 (Sa, 05 Nov 2005) $ + 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 <stdio.h> +#ifdef HAVE_STDARG_PROTOTYPES +#include <stdarg.h> +#define va_init_list(a,b) va_start(a,b) +#else +#include <varargs.h> +#define va_init_list(a,b) va_start(a) +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#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 <code>-W0</code> 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 (0x%x given)", t, TYPE(x)); + } +} + +/* exception classes */ +#include <errno.h> + +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_eKeyError; +VALUE rb_eRangeError; +VALUE rb_eNameError; +VALUE rb_eNoMethodError; +VALUE rb_eSecurityError; +VALUE rb_eNotImpError; +VALUE rb_eNoMemError; +static VALUE rb_cNameErrorMesg; + +VALUE rb_eScriptError; +VALUE rb_eSyntaxError; +VALUE rb_eLoadError; + +VALUE rb_eSystemCallError; +VALUE rb_mErrno; +static VALUE eNOERROR; + +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 <code>string.to_str</code>. + * + */ + +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 + * + * Returns the result of invoking <code>exception.to_s</code>. + * 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_message(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 + * + * <em>produces:</em> + * + * prog.rb:2:in `a' + * prog.rb:6:in `b' + * prog.rb:10 +*/ + +static VALUE +exc_backtrace(exc) + VALUE exc; +{ + ID bt = rb_intern("bt"); + + if (!rb_ivar_defined(exc, bt)) return Qnil; + return rb_ivar_get(exc, bt); +} + +static VALUE +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;i<RARRAY(bt)->len;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 <i>exc</i>. The + * argument must be an array of <code>String</code> objects in the + * format described in <code>Exception#backtrace</code>. + * + */ + +static VALUE +exc_set_backtrace(exc, bt) + VALUE exc; + VALUE bt; +{ + return rb_iv_set(exc, "bt", check_backtrace(bt)); +} + +/* + * call-seq: + * exc == obj => true or false + * + * Equality---If <i>obj</i> is not an <code>Exception</code>, returns + * <code>false</code>. Otherwise, returns <code>true</code> if <i>exc</i> and + * <i>obj</i> share same class, messages, and backtrace. + */ + +static VALUE +exc_equal(exc, obj) + VALUE exc; + VALUE obj; +{ + ID id_mesg = rb_intern("mesg"); + + if (exc == obj) return Qtrue; + if (rb_obj_class(exc) != rb_obj_class(obj)) + return Qfalse; + if (!rb_equal(rb_attr_get(exc, id_mesg), rb_attr_get(obj, id_mesg))) + return Qfalse; + if (!rb_equal(exc_backtrace(exc), exc_backtrace(obj))) + return Qfalse; + return Qtrue; +} + +/* + * 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; + } + exc_initialize(argc, argv, exc); + 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 <i>name</i> + * parameter may subsequently be examined using the <code>NameError.name</code> + * method. + */ + +static VALUE +name_err_initialize(argc, argv, self) + int argc; + VALUE *argv; + VALUE self; +{ + VALUE name; + + name = (argc > 1) ? argv[--argc] : Qnil; + exc_initialize(argc, argv, self); + 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")); + VALUE 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 <code>#name</code> method on the resulting object, and the + * arguments using the <code>#args</code> 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_equal(obj1, obj2) + VALUE obj1, obj2; +{ + VALUE *ptr1, *ptr2; + int i; + + if (obj1 == obj2) return Qtrue; + if (rb_obj_class(obj2) != rb_cNameErrorMesg) + return Qfalse; + + Data_Get_Struct(obj1, VALUE, ptr1); + Data_Get_Struct(obj2, VALUE, ptr2); + for (i=0; i<3; i++) { + if (!rb_equal(ptr1[i], ptr2[i])) + return Qfalse; + } + return Qtrue; +} + +/* :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 <code>Exception</code>. + * However, operating systems typically report errors using plain + * integers. Module <code>Errno</code> is created dynamically to map + * these operating system errors to Ruby classes, with each error + * number generating its own subclass of <code>SystemCallError</code>. + * As the subclass is created in module <code>Errno</code>, its name + * will start <code>Errno::</code>. + * + * The names of the <code>Errno::</code> classes depend on + * the environment in which Ruby runs. On a typical Unix or Windows + * platform, there are <code>Errno</code> classes such as + * <code>Errno::EACCES</code>, <code>Errno::EAGAIN</code>, + * <code>Errno::EINTR</code>, and so on. + * + * The integer operating system error number corresponding to a + * particular error is available as the class constant + * <code>Errno::</code><em>error</em><code>::Errno</code>. + * + * 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 <code>Errno</code>. + * + * 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 <code>Errno</code> class for that error, otherwise + * constructs a generic <code>SystemCallError</code> object. The + * error number is subsequently available via the <code>errno</code> + * 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; + StringValue(str); + mesg = rb_str_new(0, strlen(err)+RSTRING(str)->len+3); + sprintf(RSTRING(mesg)->ptr, "%s - %.*s", err, + (int)RSTRING(str)->len, RSTRING(str)->ptr); + rb_str_resize(mesg, strlen(RSTRING(mesg)->ptr)); + } + else { + mesg = rb_str_new2(err); + } + exc_initialize(1, &mesg, self); + 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; +} + +/* + * call-seq: + * Errno.const_missing => SystemCallError + * + * Returns default SystemCallError class. + */ +static VALUE +errno_missing(self, id) + VALUE self, id; +{ + return eNOERROR; +} + +/* + * Descendents of class <code>Exception</code> are used to communicate + * between <code>raise</code> methods and <code>rescue</code> + * statements in <code>begin/end</code> blocks. <code>Exception</code> + * objects carry information about the exception---its type (the + * exception's class name), an optional descriptive string, and + * optional traceback information. Programs may subclass + * <code>Exception</code> 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, "==", exc_equal, 1); + rb_define_method(rb_eException, "to_s", exc_to_s, 0); + rb_define_method(rb_eException, "message", exc_message, 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_eKeyError = rb_define_class("KeyError", rb_eIndexError); + 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, "==", name_err_mesg_equal, 1); + 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_singleton_method(rb_mErrno, "const_missing", errno_missing, 1); + + 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, + "The %s() function is unimplemented on this machine", + rb_id2name(ruby_frame->callee)); +} + +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; +{ + extern int errno; + 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_eRuntimeError, "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 + eNOERROR = set_syserr(0, "NOERROR"); +} + +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"); + } +} +/********************************************************************** + euc_jp.c - Oniguruma (regular expression library) +**********************************************************************/ +/*- + * Copyright (c) 2002-2005 K.Kosako <sndgk393 AT ybb DOT ne DOT jp> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "regenc.h" + +#define eucjp_islead(c) ((UChar )((c) - 0xa1) > 0xfe - 0xa1) + +static int EncLen_EUCJP[] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1 +}; + +static int +eucjp_mbc_enc_len(const UChar* p) +{ + return EncLen_EUCJP[*p]; +} + +static OnigCodePoint +eucjp_mbc_to_code(const UChar* p, const UChar* end) +{ + int c, i, len; + OnigCodePoint n; + + len = enc_len(ONIG_ENCODING_EUC_JP, p); + n = (OnigCodePoint )*p++; + if (len == 1) return n; + + for (i = 1; i < len; i++) { + if (p >= end) break; + c = *p++; + n <<= 8; n += c; + } + return n; +} + +static int +eucjp_code_to_mbclen(OnigCodePoint code) +{ + if (ONIGENC_IS_CODE_ASCII(code)) return 1; + else if ((code & 0xff0000) != 0) return 3; + else if ((code & 0xff00) != 0) return 2; + else return 0; +} + +#if 0 +static int +eucjp_code_to_mbc_first(OnigCodePoint code) +{ + int first; + + if ((code & 0xff0000) != 0) { + first = (code >> 16) & 0xff; + } + else if ((code & 0xff00) != 0) { + first = (code >> 8) & 0xff; + } + else { + return (int )code; + } + return first; +} +#endif + +static int +eucjp_code_to_mbc(OnigCodePoint code, UChar *buf) +{ + UChar *p = buf; + + if ((code & 0xff0000) != 0) *p++ = (UChar )(((code >> 16) & 0xff)); + if ((code & 0xff00) != 0) *p++ = (UChar )(((code >> 8) & 0xff)); + *p++ = (UChar )(code & 0xff); + +#if 1 + if (enc_len(ONIG_ENCODING_EUC_JP, buf) != (p - buf)) + return ONIGENCERR_INVALID_WIDE_CHAR_VALUE; +#endif + return p - buf; +} + +static int +eucjp_mbc_to_normalize(OnigAmbigType flag, + const UChar** pp, const UChar* end, UChar* lower) +{ + int len; + const UChar* p = *pp; + + if (ONIGENC_IS_MBC_ASCII(p)) { + if ((flag & ONIGENC_AMBIGUOUS_MATCH_ASCII_CASE) != 0) { + *lower = ONIGENC_ASCII_CODE_TO_LOWER_CASE(*p); + } + else { + *lower = *p; + } + + (*pp)++; + return 1; + } + else { + len = enc_len(ONIG_ENCODING_EUC_JP, p); + if (lower != p) { + int i; + for (i = 0; i < len; i++) { + *lower++ = *p++; + } + } + (*pp) += len; + return len; /* return byte length of converted char to lower */ + } +} + +static int +eucjp_is_mbc_ambiguous(OnigAmbigType flag, const UChar** pp, const UChar* end) +{ + return onigenc_mbn_is_mbc_ambiguous(ONIG_ENCODING_EUC_JP, flag, pp, end); +} + +static int +eucjp_is_code_ctype(OnigCodePoint code, unsigned int ctype) +{ + if ((ctype & ONIGENC_CTYPE_WORD) != 0) { + if (code < 128) + return ONIGENC_IS_ASCII_CODE_CTYPE(code, ctype); + else + return (eucjp_code_to_mbclen(code) > 1 ? TRUE : FALSE); + + ctype &= ~ONIGENC_CTYPE_WORD; + if (ctype == 0) return FALSE; + } + + if (code < 128) + return ONIGENC_IS_ASCII_CODE_CTYPE(code, ctype); + else + return FALSE; +} + +static UChar* +eucjp_left_adjust_char_head(const UChar* start, const UChar* s) +{ + /* In this encoding + mb-trail bytes doesn't mix with single bytes. + */ + const UChar *p; + int len; + + if (s <= start) return (UChar* )s; + p = s; + + while (!eucjp_islead(*p) && p > start) p--; + len = enc_len(ONIG_ENCODING_EUC_JP, p); + if (p + len > s) return (UChar* )p; + p += len; + return (UChar* )(p + ((s - p) & ~1)); +} + +static int +eucjp_is_allowed_reverse_match(const UChar* s, const UChar* end) +{ + const UChar c = *s; + if (c <= 0x7e || c == 0x8e || c == 0x8f) + return TRUE; + else + return FALSE; +} + +OnigEncodingType OnigEncodingEUC_JP = { + eucjp_mbc_enc_len, + "EUC-JP", /* name */ + 3, /* max enc length */ + 1, /* min enc length */ + ONIGENC_AMBIGUOUS_MATCH_ASCII_CASE, + { + (OnigCodePoint )'\\' /* esc */ + , (OnigCodePoint )ONIG_INEFFECTIVE_META_CHAR /* anychar '.' */ + , (OnigCodePoint )ONIG_INEFFECTIVE_META_CHAR /* anytime '*' */ + , (OnigCodePoint )ONIG_INEFFECTIVE_META_CHAR /* zero or one time '?' */ + , (OnigCodePoint )ONIG_INEFFECTIVE_META_CHAR /* one or more time '+' */ + , (OnigCodePoint )ONIG_INEFFECTIVE_META_CHAR /* anychar anytime */ + }, + onigenc_is_mbc_newline_0x0a, + eucjp_mbc_to_code, + eucjp_code_to_mbclen, + eucjp_code_to_mbc, + eucjp_mbc_to_normalize, + eucjp_is_mbc_ambiguous, + onigenc_ascii_get_all_pair_ambig_codes, + onigenc_nothing_get_all_comp_ambig_codes, + eucjp_is_code_ctype, + onigenc_not_support_get_ctype_code_range, + eucjp_left_adjust_char_head, + eucjp_is_allowed_reverse_match +}; +/********************************************************************** + + eval.c - + + $Author: murphy $ + $Date: 2005-11-05 04:33:55 +0100 (Sa, 05 Nov 2005) $ + 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 <stdlib.h> +#endif +#ifndef EXIT_SUCCESS +#define EXIT_SUCCESS 0 +#endif +#ifndef EXIT_FAILURE +#define EXIT_FAILURE 1 +#endif + +#include <stdio.h> +#if defined(HAVE_GETCONTEXT) && defined(HAVE_SETCONTEXT) +#include <ucontext.h> +#define USE_CONTEXT +#else +#include <setjmp.h> +#endif + +#include "st.h" +#include "dln.h" + +#ifdef __APPLE__ +#include <crt_externs.h> +#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 <alloca.h> +# else +# ifdef _AIX + #pragma alloca +# else +# ifndef alloca /* predefined by HP cc +Olibcalls */ +void *alloca (); +# endif +# endif /* AIX */ +# endif /* HAVE_ALLOCA_H */ +#endif /* __GNUC__ */ + +#ifdef HAVE_STDARG_PROTOTYPES +#include <stdarg.h> +#define va_init_list(a,b) va_start(a,b) +#else +#include <varargs.h> +#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 <unistd.h> +#endif + +#ifdef __BEOS__ +#include <net/socket.h> +#endif + +#ifdef __MACOS__ +#include "macruby_private.h" +#endif + +#ifdef USE_CONTEXT +typedef struct { + ucontext_t context; + volatile int status; +} rb_jmpbuf_t[1]; + +#undef longjmp +#undef setjmp +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 */ +} +#define longjmp(env, val) rb_jump_context(env, val) +#define setjmp(j) ((j)->status = 0, getcontext(&(j)->context), (j)->status) +#else +typedef jmp_buf rb_jmpbuf_t; +#ifndef setjmp +#ifdef HAVE__SETJMP +#define setjmp(env) _setjmp(env) +#define longjmp(env,val) _longjmp(env,val) +#endif +#endif +#endif + +#include <sys/types.h> +#include <signal.h> +#include <errno.h> + +#if defined(__VMS) +#pragma nostandard +#endif + +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif + +#include <sys/stat.h> + +VALUE rb_cProc; +static 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*)); +static VALUE rb_cMethod; +static VALUE method_call _((int, VALUE*, VALUE)); +static 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)) + +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->callee) { + rb_raise(rb_eSecurityError, "Insecure operation `%s' at level %d", + rb_id2name(ruby_frame->callee), 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->callee) { + rb_raise(rb_eSecurityError, "Insecure operation - %s", + rb_id2name(ruby_frame->callee)); + } + 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->origin == klass && ent->mid == id) { + 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; + +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); + 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(CLASS_OF(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(CLASS_OF(klass), ID_ALLOCATOR, 0, NOEX_UNDEF); +} + +static NODE* +search_method(klass, id, origin) + VALUE klass, *origin; + ID id; +{ + NODE *body; + + if (!klass) return 0; + while (!st_lookup(RCLASS(klass)->m_tbl, id, (st_data_t *)&body)) { + klass = RCLASS(klass)->super; + if (!klass) return 0; + } + + if (origin) *origin = klass; + return 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; + struct cache_entry *ent; + + ent = cache + EXPR1(klass, id); + if (ent->mid == id && ent->klass == klass && ent->method){ + return ent->method; + } + + return rb_get_method_body(&klass, &id, &noex); +} + +static void +remove_method(klass, mid) + VALUE klass; + ID mid; +{ + NODE *body; + + 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_delete(RCLASS(klass)->m_tbl, &mid, (st_data_t *)&body) || + !body->nd_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 <code>Module.undef_method</code>. + */ + +static VALUE +rb_mod_remove_method(argc, argv, mod) + int argc; + VALUE *argv; + VALUE mod; +{ + int i; + + for (i=0; i<argc; i++) { + remove_method(mod, rb_to_id(argv[i])); + } + return mod; +} + +#undef rb_disable_super +#undef rb_enable_super + +void +rb_disable_super(klass, name) + VALUE klass; + const char *name; +{ + /* obsolete - no use */ +} + +void +rb_enable_super(klass, name) + VALUE klass; + const char *name; +{ + rb_warning("rb_enable_super() is obsolete"); +} + +static void +rb_export_method(klass, name, noex) + VALUE klass; + ID name; + ID noex; +{ + NODE *body; + VALUE origin; + + if (klass == rb_cObject) { + rb_secure(4); + } + body = search_method(klass, name, &origin); + if (!body && TYPE(klass) == T_MODULE) { + body = search_method(rb_cObject, name, &origin); + } + if (!body || !body->nd_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; + + 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"); + } + buf = ALLOCA_N(char,strlen(name)+2); + sprintf(buf, "@%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); + } +} + +VALUE ruby_errinfo = Qnil; +extern int ruby_nerrs; + +static VALUE rb_eLocalJumpError; +static 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 { \ + 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 +#define BLOCK_FROM_METHOD 4 + +static struct BLOCK *ruby_block; +static unsigned long block_unique = 0; + +#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 + +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 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 PROT_TOP INT2FIX(4) /* 9 */ + +#define EXEC_TAG() (FLUSH_REGISTER_WINDOWS, setjmp(prot_tag->buf)) + +#define JUMP_TAG(st) do { \ + ruby_frame = prot_tag->frame; \ + ruby_iter = prot_tag->iter; \ + 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_CONTCALL 0x9 +#define TAG_THREAD 0xa +#define TAG_MASK 0xf + +VALUE ruby_class; +static VALUE ruby_wrapper; /* security wrapper */ + +#define PUSH_CLASS(c) do { \ + VALUE _class = ruby_class; \ + ruby_class = (c) + +#define POP_CLASS() ruby_class = _class; \ +} while (0) + +static NODE *ruby_cref = 0; +static NODE *top_cref; +#define PUSH_CREF(c) ruby_cref = NEW_NODE(NODE_CREF,(c),0,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 + +typedef struct thread * rb_thread_t; +static rb_thread_t curr_thread = 0; +static rb_thread_t main_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) + +struct ruby_env { + struct ruby_env *prev; + struct FRAME *frame; + struct SCOPE *scope; + struct BLOCK *block; + struct iter *iter; + struct tag *tag; + NODE *cref; +}; + +static void push_thread_anchor _((struct ruby_env *)); +static void pop_thread_anchor _((struct ruby_env *)); + +#define PUSH_THREAD_TAG() PUSH_TAG(PROT_THREAD); \ + do { \ + struct ruby_env _interp; \ + push_thread_anchor(&_interp); +#define POP_THREAD_TAG() \ + pop_thread_anchor(&_interp); \ + } while (0); \ + POP_TAG() + +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)); +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; \ + \ + for (hook = event_hooks; hook; hook = hook->next) { \ + if (hook->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->callee) { + warn_printf("%s:%d:in `%s'", ruby_sourcefile, ruby_sourceline, + rb_id2name(ruby_frame->callee)); + } + else if (ruby_sourceline == 0) { + warn_printf("%s", ruby_sourcefile); + } + else { + warn_printf("%s:%d", ruby_sourcefile, ruby_sourceline); + } + } +} + +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_array_type(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)) { + 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) { + warn_print2(tail, elen-len-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; i<ep->len; 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 + +NORETURN(static void rb_thread_start_1 _((void))); + +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(); + 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; + top_cref = rb_node_newnode(NODE_CREF,rb_cObject,0,0); + ruby_cref = top_cref; + rb_define_global_const("TOPLEVEL_BINDING", rb_f_binding(ruby_top_self)); +#ifdef __MACOS__ + _macruby_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; +{ + if (!node) return Qnil; + if (nd_type(node) == NODE_PRELUDE) { + rb_eval(self, node->nd_head); + node = node->nd_body; + } + 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_set_raised(); +static int thread_reset_raised(); + +static VALUE exception_error; +static VALUE sysstack_error; + +static int +error_handle(ex) + int ex; +{ + int status = EXIT_FAILURE; + + if (thread_set_raised()) 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)) { + VALUE st = rb_iv_get(ruby_errinfo, "status"); + status = NUM2INT(st); + } + else { + error_print(); + } + break; + default: + rb_bug("Unknown longjmp status %d", ex); + break; + } + thread_reset_raised(); + return status; +} + +void +ruby_options(argc, argv) + int argc; + char **argv; +{ + int state; + +#ifdef _WIN32 + argc = rb_w32_cmdvector(GetCommandLine(), &argv); +#endif + + Init_stack((void*)&state); + PUSH_THREAD_TAG(); + if ((state = EXEC_TAG()) == 0) { + ruby_process_options(argc, argv); + } + else { + if (state == TAG_THREAD) { + rb_thread_start_1(); + } + trace_func = 0; + tracing = 0; + exit(error_handle(state)); + } + POP_THREAD_TAG(); + +#ifdef _WIN32_WCE + wce_FreeCommandLine(); +#endif +} + +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 err = ruby_errinfo; + + ruby_safe_level = 0; + Init_stack((void*)&state); + PUSH_THREAD_TAG(); + PUSH_ITER(ITER_NOT); + if ((state = EXEC_TAG()) == 0) { + ruby_finalize_0(); + if (ruby_errinfo) err = ruby_errinfo; + rb_thread_cleanup(); + rb_thread_wait_other_threads(); + } + else if (state == TAG_THREAD) { + rb_thread_start_1(); + } + else if (ex == 0) { + ex = state; + } + POP_ITER(); + ruby_errinfo = err; + ex = error_handle(ex); + ruby_finalize_1(); + POP_THREAD_TAG(); + + if (err && rb_obj_is_kind_of(err, rb_eSystemExit)) { + VALUE st = rb_iv_get(err, "status"); + return NUM2INT(st); + } + return ex; +} + +extern NODE *ruby_eval_tree; + +static void cont_call _((VALUE)); + +static int +ruby_exec_internal() +{ + int state; + + PUSH_THREAD_TAG(); + 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); + } +#if 0 + else if (state == TAG_CONTCALL) { + cont_call(prot_tag->retval); + } +#endif + else if (state == TAG_THREAD) { + rb_thread_start_1(); + } + POP_ITER(); + POP_THREAD_TAG(); + return state; +} + +int +ruby_exec() +{ + volatile NODE *tmp; + + Init_stack((void*)&tmp); + return ruby_exec_internal(); +} + +void +ruby_stop(ex) + int ex; +{ + exit(ruby_cleanup(ex)); +} + +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->callee = 0; + ruby_frame->this_func = 0; + ruby_frame->this_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->callee = 0; + ruby_frame->this_func = 0; + ruby_frame->this_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(); + + 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)) return rb_const_defined(CLASS_OF(self), id); + 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)) return rb_const_get(CLASS_OF(self), id); + while (RCLASS(klass)->iv_tbl && st_lookup(RCLASS(klass)->iv_tbl, id, &result)) { + if (result == Qundef) { + rb_autoload_load(klass, id); + continue; + } + return result; + } + cbase = cbase->nd_next; + } + return rb_const_get(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] + * + * <em>produces:</em> + * + * ["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 <code>remove_method</code>, 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 + * + * <em>produces:</em> + * + * In child + * In parent + * prog.rb:23: undefined method `hello' for #<Child:0x401b3bb4> (NoMethodError) + */ + +static VALUE +rb_mod_undef_method(argc, argv, mod) + int argc; + VALUE *argv; + VALUE mod; +{ + int i; + + for (i=0; i<argc; i++) { + rb_undef(mod, rb_to_id(argv[i])); + } + return mod; +} + +void +rb_alias(klass, name, def) + VALUE klass; + ID name, def; +{ + VALUE origin; + NODE *orig, *body, *node; + VALUE singleton = 0; + + rb_frozen_class_p(klass); + if (name == def) return; + if (klass == rb_cObject) { + rb_secure(4); + } + orig = search_method(klass, def, &origin); + if (!orig || !orig->nd_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, (st_data_t *)&node)) { + 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), orig->nd_noex)); + 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 <i>new_name</i> a new copy of the method <i>old_name</i>. 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) + * + * <em>produces:</em> + * + * 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; +} + +static NODE* +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 = rb_node_newnode(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,alen) do {\ + NODE *n = anode;\ + if (!n) {\ + argc = 0;\ + argv = 0;\ + }\ + else if (nd_type(n) == NODE_ARRAY) {\ + argc=alen;\ + if (argc > 0) {\ + int i;\ + n = anode;\ + argv = TMP_ALLOC(argc);\ + for (i=0;i<argc;i++) {\ + argv[i] = rb_eval(self,n->nd_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 = ALLOCA_N(VALUE, argc);\ + MEMCPY(argv, RARRAY(args)->ptr, VALUE, argc);\ + }\ +} while (0) + +#define SETUP_ARGS(anode) SETUP_ARGS0(anode, anode->nd_alen) + +#define BEGIN_CALLARGS do {\ + struct BLOCK *tmp_block = ruby_block;\ + int tmp_iter = ruby_iter->iter;\ + if (tmp_iter == ITER_PRE) {\ + ruby_block = ruby_block->outer;\ + 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 const char* is_defined _((VALUE, NODE*, char*, int)); + +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;i<argc;i++) { + if (!is_defined(self, node->nd_head, buf, 0)) + return 0; + node = node->nd_next; + } + } + } + else if (!is_defined(self, node, buf, 0)) { + return 0; + } + return type; +} + +static const char* +is_defined(self, node, buf, noeval) + VALUE self; + NODE *node; /* OK */ + char *buf; + int noeval; +{ + VALUE val; /* OK */ + int state; + static const char *ex = "expression"; + + if (!node) return ex; + switch (nd_type(node)) { + case NODE_SUPER: + case NODE_ZSUPER: + if (ruby_frame->this_func == 0) return 0; + else if (ruby_frame->this_class == 0) return 0; + val = ruby_frame->this_class; + if (rb_method_boundp(RCLASS(val)->super, ruby_frame->this_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: + if (!is_defined(self, node->nd_recv, buf, Qtrue)) return 0; + if (noeval) return ex; + val = rb_eval(self, node->nd_recv); + 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_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: + if (!is_defined(self, node->nd_recv, buf, Qtrue)) return 0; + if (noeval) return ex; + val = rb_eval(self, node->nd_recv); + 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))) { + if (!buf) return ex; + sprintf(buf, "$%d", (int)node->nd_nth); + return buf; + } + break; + + case NODE_BACK_REF: + if (RTEST(rb_reg_nth_defined(0, MATCH_DATA))) { + if (!buf) return ex; + sprintf(buf, "$%c", (char)node->nd_nth); + return buf; + } + break; + + default: + PUSH_TAG(PROT_NONE); + if ((state = EXEC_TAG()) == 0) { + rb_eval(self, node); + } + POP_TAG(); + if (!state) { + return ex; + } + 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: <code>c-call</code> + * (call a C-language routine), <code>c-return</code> (return from a + * C-language routine), <code>call</code> (call a Ruby method), + * <code>class</code> (start a class or module definition), + * <code>end</code> (finish a class or module definition), + * <code>line</code> (execute code on a new line), <code>raise</code> + * (raise an exception), and <code>return</code> (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; + + 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; + + if (!trace_func) return; + if (tracing) return; + if (id == ID_ALLOCATOR) return; + if (!node && ruby_sourceline == 0) return; + + if (!(node_save = ruby_current_node)) { + node_save = NEW_BEGIN(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 = self; + } + } + PUSH_TAG(PROT_NONE); + raised = thread_reset_raised(); + 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?klass:Qnil), + Qundef, 0); + } + if (raised) thread_set_raised(); + 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_values_new2(0, 0); + tmp = rb_check_array_type(v); + if (NIL_P(tmp)) { + return rb_values_new(1, v); + } + /* no lhs means splat lhs only */ + if (!lhs) { + return rb_values_new(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; +} + +static VALUE +splat_value(v) + VALUE v; +{ + VALUE val; + + if (NIL_P(v)) val = rb_ary_new3(1, Qnil); + else val = rb_Array(v); + return rb_values_from_ary(val); +} + +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))); + +static VALUE +rb_eval(self, n) + VALUE self; + NODE *n; +{ + NODE * volatile contnode = 0; + NODE * volatile node = n; + int state; + volatile VALUE result = Qnil; + +#define RETURN(v) do { \ + result = (v); \ + goto finish; \ +} while (0) + + again: + if (!node) RETURN(Qnil); + + ruby_current_node = node; + if (node->flags & NODE_NEWLINE) { + EXEC_EVENT_HOOK(RUBY_EVENT_LINE, node, self, + ruby_frame->this_func, + ruby_frame->this_class); + } + 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_ERRINFO: + RETURN(ruby_errinfo); + + case NODE_IF: + EXEC_EVENT_HOOK(RUBY_EVENT_LINE, node, self, + ruby_frame->this_func, + ruby_frame->this_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->this_func, + ruby_frame->this_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; i<RARRAY(v)->len; 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->this_func, + ruby_frame->this_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; i<RARRAY(v)->len; 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: + case NODE_LAMBDA: + { + 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 if (nd_type(node) == NODE_LAMBDA) { + ruby_iter->iter = ruby_frame->iter = ITER_CUR; + result = rb_block_proc(); + } + 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); + } + POP_ITER(); + } + else if (state == TAG_BREAK && TAG_DST()) { + result = prot_tag->retval; + state = 0; + } + else if (state == TAG_RETRY && ruby_block == &_block) { + 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; + return_value(rb_eval(self, node->nd_stts)); + JUMP_TAG(TAG_NEXT); + 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) { + if (state == TAG_NEXT) prot_tag->retval = result; + 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) { + 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: + result = rb_range_new(rb_eval(self, node->nd_beg), + rb_eval(self, node->nd_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); + 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); + } + 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); + } + break; + + case NODE_VCALL: + SET_CURRENT_SOURCE(); + result = rb_call(CLASS_OF(self),self,node->nd_mid,0,0,2); + break; + + case NODE_SUPER: + case NODE_ZSUPER: + { + int argc; VALUE *argv; /* used in SETUP_ARGS */ + TMP_PROTECT; + + if (ruby_frame->this_class == 0) { + if (ruby_frame->this_func) { + rb_name_error(ruby_frame->callee, + "superclass method `%s' disabled", + rb_id2name(ruby_frame->this_func)); + } + else { + rb_raise(rb_eNoMethodError, "super called outside of method"); + } + } + if (nd_type(node) == NODE_ZSUPER) { + argc = ruby_frame->argc; + if (argc && ruby_frame->prev && + (ruby_frame->prev->flags & FRAME_DMETH)) { + 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 { + 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; + NODE *rval; + TMP_PROTECT; + + recv = rb_eval(self, node->nd_recv); + rval = node->nd_args->nd_head; + SETUP_ARGS0(node->nd_args->nd_next, node->nd_args->nd_alen - 1); + val = rb_funcall2(recv, aref, argc-1, 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: + val = rb_funcall(val, node->nd_mid, 1, rb_eval(self, rval)); + } + argv[argc-1] = val; + rb_funcall2(recv, aset, argc, argv); + result = val; + } + break; + + case NODE_OP_ASGN2: + { + ID id = node->nd_next->nd_vid; + VALUE recv, val; + + recv = rb_eval(self, node->nd_recv); + val = rb_funcall(recv, id, 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: + val = rb_funcall(val, node->nd_next->nd_mid, 1, + rb_eval(self, node->nd_value)); + } + + 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, 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 { + if (NIL_P(ruby_cbase)) { + rb_raise(rb_eTypeError, "no class/module to define constant"); + } + 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_VALUES: + { + VALUE val; + long i; + + i = node->nd_alen; + val = rb_values_new2(i, 0); + for (i=0;node;node=node->nd_next) { + RARRAY(val)->ptr[i++] = rb_eval(self, node->nd_head); + RARRAY(val)->len = i; + } + + result = val; + } + 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 = 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, (st_data_t *)&body)) { + 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 = 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 singleton 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]; + const char *desc = is_defined(self, node->nd_head, buf, 0); + + if (desc) result = rb_str_new2(desc); + else result = Qnil; + } + break; + + default: + rb_bug("unknown node type %d", nd_type(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->this_func, ruby_frame->this_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->this_func, + ruby_frame->this_class); + if (state) JUMP_TAG(state); + + return result; +} + +static NODE *basic_respond_to = 0; + +int +rb_respond_to(obj, id) + VALUE obj; + ID id; +{ + VALUE klass = CLASS_OF(obj); + if (rb_method_node(klass, respond_to) == basic_respond_to && + rb_method_boundp(klass, id, 0)) { + return Qtrue; + } + else{ + return rb_funcall(obj, respond_to, 1, ID2SYM(id)); + } + return 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 +rb_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, const char *, long))); +static VALUE +terminate_process(status, mesg, mlen) + int status; + const char *mesg; + long mlen; +{ + VALUE args[2]; + args[0] = INT2NUM(status); + args[1] = rb_str_new(mesg, mlen); + + rb_exc_raise(rb_class_new_instance(2, args, rb_eSystemExit)); +} + +void +rb_exit(status) + int status; +{ + if (prot_tag) { + terminate_process(status, "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 + * <code>SystemExit</code> 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" + * + * <em>produces:</em> + * + * rescued a SystemExit exception + * after begin block + * + * Just prior to termination, Ruby executes any <code>at_exit</code> 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 + * + * <em>produces:</em> + * + * 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); + 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 + * <code>Kernel.exit(1)</code>. 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(argv[0]); + rb_io_puts(argc, argv, rb_stderr); + terminate_process(EXIT_FAILURE, RSTRING(argv[0])->ptr, RSTRING(argv[0])->len); + } + 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; + + if (thread_set_raised()) { + 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(); + 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) { + e = rb_obj_as_string(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) { + thread_reset_raised(); + 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->this_func, + ruby_frame->this_class); + } + if (!prot_tag) { + error_print(); + } + thread_reset_raised(); + JUMP_TAG(tag); +} + +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 <code>$!</code> or raises + * a <code>RuntimeError</code> if <code>$!</code> 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 <code>begin...end</code> 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 <code>true</code> if <code>yield</code> would execute a + * block in the current context. The <code>iterator?</code> 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; +} + +static 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); +} + +NORETURN(static void return_jump(VALUE)); +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->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; + default: + break; + } + tt = tt->prev; + } + localjump_error("unexpected break", retval, TAG_BREAK); +} + +static VALUE bmcall _((VALUE, VALUE)); +static int method_arity _((VALUE)); + +static VALUE +rb_yield_0(val, self, klass, flags, avalue) + VALUE val, self, klass; /* OK */ + int flags, avalue; +{ + NODE *node, *var; + 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; + + if (!rb_block_given_p()) { + localjump_error("no block given", Qnil, 0); + } + + PUSH_VARS(); + block = ruby_block; + frame = block->frame; + frame.prev = ruby_frame; + 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; + var = block->var; + + if (var) { + PUSH_TAG(PROT_NONE); + if ((state = EXEC_TAG()) == 0) { + NODE *bvar = NULL; + block_var: + if (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 (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 (!bvar && nd_type(var) == NODE_BLOCK_PASS) { + bvar = var->nd_body; + var = var->nd_args; + goto block_var; + } + else if (nd_type(var) == NODE_MASGN) { + if (!avalue) { + val = svalue_to_mrhs(val, var->nd_head); + } + massign(self, 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 = 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, var, val, lambda); + } + if (bvar) { + VALUE blk; + if (flags & YIELD_PROC_CALL) + blk = block->block_obj; + else + blk = rb_block_proc(); + assign(self, bvar, blk, 0); + } + } + POP_TAG(); + if (state) goto pop_state; + } + else if (lambda && RARRAY(val)->len != 0 && + (!node || nd_type(node) != NODE_IFUNC || + node->nd_cfnc != bmcall)) { + rb_raise(rb_eArgError, "wrong number of arguments (%ld for 0)", + RARRAY(val)->len); + } + 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; + } + if ((block->flags&BLOCK_FROM_METHOD) && RTEST(block->block_obj)) { + struct BLOCK *data, _block; + Data_Get_Struct(block->block_obj, struct BLOCK, data); + _block = *data; + _block.outer = ruby_block; + _block.uniq = block_unique++; + ruby_block = &_block; + PUSH_ITER(ITER_PRE); + ruby_frame->iter = ITER_CUR; + result = (*node->nd_cfnc)(val, node->nd_tval, self); + POP_ITER(); + } + else { + 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: + 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 +{ + int i; + va_list args; + VALUE val; + + if (n == 0) { + return rb_yield_0(Qundef, 0, 0, 0, Qfalse); + } + val = rb_values_new2(n, 0); + va_init_list(args, n); + for (i=0; i<n; i++) { + RARRAY(val)->ptr[i] = va_arg(args, VALUE); + } + RARRAY(val)->len = n; + va_end(args); + return rb_yield_0(val, 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 && i<len; i++) { + assign(self, list->nd_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 && i<len) { + assign(self, node->nd_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); + } + 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); + } + } + 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_ITER(ITER_PRE); + PUSH_TAG(PROT_LOOP); + PUSH_BLOCK(0, node); + 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_BLOCK(); + POP_TAG(); + POP_ITER(); + + 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; + va_list args; + + PUSH_TAG(PROT_NONE); + if ((state = EXEC_TAG()) == 0) { + retry_entry: + result = (*b_proc)(data1); + } + else if (state == TAG_RAISE) { + int handle = Qfalse; + VALUE eclass; + + va_init_list(args, data2); + while (eclass = va_arg(args, VALUE)) { + if (rb_obj_is_kind_of(ruby_errinfo, eclass)) { + handle = Qtrue; + break; + } + } + va_end(args); + + if (handle) { + if (r_proc) { + PUSH_TAG(PROT_NONE); + if ((state = EXEC_TAG()) == 0) { + result = (*r_proc)(data2, ruby_errinfo); + } + POP_TAG(); + if (state == TAG_RETRY) { + state = 0; + ruby_errinfo = Qnil; + goto retry_entry; + } + } + else { + result = Qnil; + state = 0; + } + if (state == 0) { + 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_THREAD_TAG(); + cont_protect = (VALUE)rb_node_newnode(NODE_MEMO, cont_protect, 0, 0); + if ((status = EXEC_TAG()) == 0) { + result = (*proc)(data); + } + else if (status == TAG_THREAD) { + rb_thread_start_1(); + } + cont_protect = ((NODE *)cont_protect)->u1.value; + POP_THREAD_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 */ + (*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 inline void +stack_check() +{ + static int overflowing = 0; + + if (!overflowing && ruby_stack_check()) { + int state; + overflowing = 1; + PUSH_TAG(PROT_NONE); + if ((state = EXEC_TAG()) == 0) { + rb_exc_raise(sysstack_error); + } + POP_TAG(); + overflowing = 0; + JUMP_TAG(state); + } +} + +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 <i>obj</i> is sent a message it cannot handle. + * <i>symbol</i> is the symbol for the method called, and <i>args</i> + * 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 <code>Roman</code>, 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)); + } + + nargv = ALLOCA_N(VALUE, argc+1); + nargv[0] = ID2SYM(id); + MEMCPY(nargv+1, argv, VALUE, argc); + + 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, nosuper) + VALUE klass, recv; + ID id; + ID oid; + int argc; /* OK */ + VALUE *argv; /* OK */ + NODE *body; /* OK */ + int nosuper; +{ + NODE *b2; /* OK */ + volatile VALUE result = Qnil; + int itr; + static int tick; + volatile VALUE args; + TMP_PROTECT; + + switch (ruby_iter->iter) { + case ITER_PRE: + 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) { + argc = -argc-1; + args = rb_ary_concat(rb_ary_new4(argc, argv), splat_value(argv[argc])); + argc = RARRAY(args)->len; + argv = RARRAY(args)->ptr; + } + PUSH_ITER(itr); + PUSH_FRAME(); + ruby_frame->callee = id; + ruby_frame->this_func = oid; + ruby_frame->this_class = nosuper?0:klass; + ruby_frame->self = recv; + ruby_frame->argc = argc; + + 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: /* visibility override */ + result = rb_call_super(argc, argv); + break; + + case NODE_BMETHOD: + ruby_frame->flags |= FRAME_DMETH; + result = proc_invoke(body->nd_cval, rb_ary_new4(argc, argv), recv, 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; + + PUSH_VARS(); + PUSH_TAG(PROT_FUNC); + + if ((state = EXEC_TAG()) == 0) { + NODE *node = 0; + int i; + + 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 ((long)node->nd_rest == -1) { + int opt = i; + NODE *optnode = node->nd_opt; + + while (optnode) { + opt++; + optnode = optnode->nd_next; + } + if (opt < argc) { + rb_raise(rb_eArgError, "wrong number of arguments (%d for %d)", + argc, opt); + } + ruby_frame->argc = opt; + } + + 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--; + opt = opt->nd_next; + } + if (opt) { + rb_eval(recv, opt); + } + } + if ((long)node->nd_rest >= 0) { + VALUE v; + + if (argc > 0) + v = rb_ary_new4(argc,argv); + else + v = rb_ary_new2(0); + ruby_scope->local_vars[node->nd_rest] = v; + } + } + } + if ((long)node->nd_rest >= 0) { + ruby_frame->argc = -(ruby_frame->argc - argc)-1; + } + + 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(); + POP_VARS(); + POP_CLASS(); + POP_SCOPE(); + ruby_cref = saved_cref; + if (event_hooks) { + EXEC_EVENT_HOOK(RUBY_EVENT_RETURN, body, recv, id, klass); + } + 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: + rb_bug("unknown node type %d", nd_type(body)); + break; + } + POP_FRAME(); + POP_ITER(); + return result; +} + +static VALUE +rb_call(klass, recv, mid, argc, argv, scope) + VALUE klass, recv; + ID mid; + int argc; /* OK */ + const VALUE *argv; /* OK */ + int scope; +{ + 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) { + /* receiver specified form for private method */ + if ((noex & NOEX_PRIVATE) && scope == 0) + 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 (TYPE(defined_class) == T_ICLASS) { + defined_class = RBASIC(defined_class)->klass; + } + if (!rb_obj_is_kind_of(ruby_frame->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 & NOEX_NOSUPER); +} + +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); +} + +/* + * 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 <code>__send__</code> 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); + POP_ITER(); + + return vid; +} + +VALUE +#ifdef HAVE_STDARG_PROTOTYPES +rb_funcall(VALUE recv, ID mid, int n, ...) +#else +rb_funcall(recv, mid, n, va_alist) + VALUE recv; + ID mid; + int n; + va_dcl +#endif +{ + VALUE *argv; + va_list ar; + va_init_list(ar, n); + + if (n > 0) { + long i; + + argv = ALLOCA_N(VALUE, n); + + for (i=0;i<n;i++) { + argv[i] = va_arg(ar, VALUE); + } + va_end(ar); + } + else { + argv = 0; + } + + return rb_call(CLASS_OF(recv), recv, mid, n, argv, 1); +} + +VALUE +rb_funcall2(recv, mid, argc, argv) + VALUE recv; + ID mid; + int argc; + const VALUE *argv; +{ + return rb_call(CLASS_OF(recv), recv, mid, argc, argv, 1); +} + +VALUE +rb_funcall3(recv, mid, argc, argv) + VALUE recv; + ID mid; + int argc; + const VALUE *argv; +{ + return rb_call(CLASS_OF(recv), recv, mid, argc, argv, 0); +} + +VALUE +rb_call_super(argc, argv) + int argc; + const VALUE *argv; +{ + VALUE result, self, klass, k; + + if (ruby_frame->this_class == 0) { + rb_name_error(ruby_frame->callee, "calling `super' from `%s' is prohibited", + rb_id2name(ruby_frame->this_func)); + } + + self = ruby_frame->self; + klass = ruby_frame->this_class; + + PUSH_ITER(ruby_iter->iter ? ITER_PRE : ITER_NOT); + result = rb_call(RCLASS(klass)->super, self, ruby_frame->this_func, argc, argv, 3); + POP_ITER(); + + return result; +} + +static VALUE +backtrace(lev) + int lev; +{ + struct FRAME *frame = ruby_frame; + char buf[BUFSIZ]; + volatile VALUE ary; + NODE *n; + + ary = rb_ary_new(); + if (frame->this_func == ID_ALLOCATOR) { + frame = frame->prev; + } + if (lev < 0) { + ruby_set_current_source(); + if (frame->this_func) { + snprintf(buf, BUFSIZ, "%s:%d:in `%s'", + ruby_sourcefile, ruby_sourceline, + rb_id2name(frame->this_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; + } + } + } + while (frame && (n = frame->node)) { + if (frame->prev && frame->prev->this_func) { + snprintf(buf, BUFSIZ, "%s:%d:in `%s'", + n->nd_file, nd_line(n), + rb_id2name(frame->prev->this_func)); + } + else { + snprintf(buf, BUFSIZ, "%s:%d", n->nd_file, nd_line(n)); + } + rb_ary_push(ary, rb_str_new2(buf)); + frame = frame->prev; + } + + return ary; +} + +/* + * call-seq: + * caller(start=1) => array + * + * Returns the current execution stack---an array containing strings in + * the form ``<em>file:line</em>'' or ``<em>file:line: in + * `method'</em>''. 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; i<RARRAY(ary)->len; i++) { + printf("\tfrom %s\n", RSTRING(RARRAY(ary)->ptr[i])->ptr); + } +} + +static VALUE +make_backtrace() +{ + return backtrace(-1); +} + +ID +rb_frame_this_func() +{ + return ruby_frame->this_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(ruby_cbase); + 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--; + ruby_safe_level = safe; + 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; + + errat = get_backtrace(ruby_errinfo); + mesg = rb_attr_get(ruby_errinfo, rb_intern("mesg")); + if (!NIL_P(errat) && TYPE(errat) == T_ARRAY) { + 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(backtrace(-2))->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 <em>string</em>. If + * <em>binding</em> is given, the evaluation is performed in its + * context. The binding may be a <code>Binding</code> object or a + * <code>Proc</code> object. If the optional <em>filename</em> and + * <em>lineno</em> 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; + + PUSH_CLASS(under); + PUSH_FRAME(); + ruby_frame->self = _frame.prev->self; + ruby_frame->callee = _frame.prev->callee; + ruby_frame->this_func = _frame.prev->this_func; + ruby_frame->this_class = _frame.prev->this_class; + ruby_frame->argc = _frame.prev->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; +{ + 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->callee), + rb_id2name(ruby_frame->callee)); + } + 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 <code>instance_eval</code> + * 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 (FIXNUM_P(self) || SYMBOL_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. <code>module_eval</code> 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) + * + * <em>produces:</em> + * + * 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 callee, this_func; + volatile VALUE wrapper = ruby_wrapper; + volatile VALUE self = ruby_top_self; + NODE * volatile last_node; + NODE *saved_cref = ruby_cref; + TMP_PROTECT; + + if (!wrap) rb_secure(4); + FilePathValue(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 = 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->callee = 0; + ruby_frame->this_func = 0; + ruby_frame->this_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(); + callee = ruby_frame->callee; + this_func = ruby_frame->this_func; + last_node = ruby_current_node; + if (!ruby_current_node && ruby_sourcefile) { + last_node = NEW_BEGIN(0); + } + ruby_current_node = 0; + if (state == 0) { + NODE * volatile 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->callee = callee; + ruby_frame->this_func = this_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_THREAD_TAG(); + if ((status = EXEC_TAG()) == 0) { + rb_load(fname, wrap); + } + else if (status == TAG_THREAD) { + rb_thread_start_1(); + } + POP_THREAD_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 <code>$:</code>. 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 char * +rb_feature_p(feature, ext, rb) + const char *feature, *ext; + int rb; +{ + VALUE v; + char *f, *e; + long i, len, elen; + + if (ext) { + len = ext - feature; + elen = strlen(ext); + } + else { + len = strlen(feature); + elen = 0; + } + for (i = 0; i < RARRAY(rb_features)->len; ++i) { + v = RARRAY(rb_features)->ptr[i]; + f = StringValuePtr(v); + if (strncmp(f, feature, len) != 0) continue; + if (!*(e = f + len)) { + if (ext) continue; + return e; + } + if (*e != '.') continue; + if ((!rb || !ext) && (IS_SOEXT(e) || IS_DLEXT(e))) { + return e; + } + if ((rb || !ext) && (strcmp(e, ".rb") == 0)) { + return e; + } + } + return 0; +} + +static const char *const loadable_ext[] = { + ".rb", DLEXT, +#ifdef DLEXT2 + DLEXT2, +#endif + 0 +}; + +static int search_required _((VALUE, VALUE *)); + +int +rb_provided(feature) + const char *feature; +{ + int i; + char *buf; + VALUE fname; + + if (rb_feature_p(feature, 0, Qfalse)) + return Qtrue; + if (loading_tbl) { + if (st_lookup(loading_tbl, (st_data_t)feature, 0)) return Qtrue; + buf = ALLOCA_N(char, strlen(feature)+8); + strcpy(buf, feature); + for (i=0; loadable_ext[i]; i++) { + strcpy(buf+strlen(feature), loadable_ext[i]); + if (st_lookup(loading_tbl, (st_data_t)buf, 0)) return Qtrue; + } + } + if (search_required(rb_str_new2(feature), &fname)) { + feature = RSTRING(fname)->ptr; + if (rb_feature_p(feature, 0, Qfalse)) + return Qtrue; + if (loading_tbl && st_lookup(loading_tbl, (st_data_t)feature, 0)) + 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 int +load_wait(ftptr) + char *ftptr; +{ + st_data_t th; + + if (!loading_tbl) return Qfalse; + if (!st_lookup(loading_tbl, (st_data_t)ftptr, &th)) return Qfalse; + if ((rb_thread_t)th == curr_thread) return Qtrue; + do { + CHECK_INTS; + rb_thread_schedule(); + } while (st_lookup(loading_tbl, (st_data_t)ftptr, &th)); + return Qtrue; +} + +/* + * 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 <code>$:</code>. 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 + * <code>$"</code>. A feature will not be loaded if it's name already + * appears in <code>$"</code>. However, the file name is not converted + * to an absolute path, so that ``<code>require 'a';require + * './a'</code>'' will load <code>a.rb</code> 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, path) + VALUE fname, *path; +{ + VALUE tmp; + char *ext, *ftptr; + int type; + + *path = 0; + ext = strrchr(ftptr = RSTRING(fname)->ptr, '.'); + if (ext && !strchr(ext, '/')) { + if (strcmp(".rb", ext) == 0) { + if (rb_feature_p(ftptr, ext, Qtrue)) return 'r'; + if (tmp = rb_find_file(fname)) { + tmp = rb_file_expand_path(tmp, Qnil); + ext = strrchr(ftptr = RSTRING(tmp)->ptr, '.'); + if (!rb_feature_p(ftptr, ext, Qtrue)) + *path = tmp; + return 'r'; + } + return 0; + } + else if (IS_SOEXT(ext)) { + if (rb_feature_p(ftptr, ext, Qfalse)) return 's'; + tmp = rb_str_new(RSTRING(fname)->ptr, ext-RSTRING(fname)->ptr); +#ifdef DLEXT2 + OBJ_FREEZE(tmp); + if (rb_find_file_ext(&tmp, loadable_ext+1)) { + tmp = rb_file_expand_path(tmp, Qnil); + ext = strrchr(ftptr = RSTRING(tmp)->ptr, '.'); + if (!rb_feature_p(ftptr, ext, Qfalse)) + *path = tmp; + return 's'; + } +#else + rb_str_cat2(tmp, DLEXT); + OBJ_FREEZE(tmp); + if (tmp = rb_find_file(tmp)) { + tmp = rb_file_expand_path(tmp, Qnil); + ext = strrchr(ftptr = RSTRING(tmp)->ptr, '.'); + if (!rb_feature_p(ftptr, ext, Qfalse)) + *path = tmp; + return 's'; + } +#endif + } + else if (IS_DLEXT(ext)) { + if (rb_feature_p(ftptr, ext, Qfalse)) return 's'; + if (tmp = rb_find_file(fname)) { + tmp = rb_file_expand_path(tmp, Qnil); + ext = strrchr(ftptr = RSTRING(tmp)->ptr, '.'); + if (!rb_feature_p(ftptr, ext, Qfalse)) + *path = tmp; + return 's'; + } + } + } + else if (ext = rb_feature_p(ftptr, 0, Qfalse)) { + return (*ext && (IS_SOEXT(ext) || IS_DLEXT(ext))) ? 's' : 'r'; + } + tmp = fname; + type = rb_find_file_ext(&tmp, loadable_ext); + tmp = rb_file_expand_path(tmp, Qnil); + switch (type) { + case 0: + ftptr = RSTRING(tmp)->ptr; + if ((ext = rb_feature_p(ftptr, 0, Qfalse))) { + type = strcmp(".rb", ext); + break; + } + return 0; + + default: + ext = strrchr(ftptr = RSTRING(tmp)->ptr, '.'); + if (rb_feature_p(ftptr, ext, !--type)) break; + *path = tmp; + } + 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 this_func, callee; + int vmode, safe; + } volatile saved; + char *volatile ftptr = 0; + + saved.vmode = scope_vmode; + saved.node = ruby_current_node; + saved.callee = ruby_frame->callee; + saved.this_func = ruby_frame->this_func; + saved.safe = ruby_safe_level; + PUSH_TAG(PROT_NONE); + if ((state = EXEC_TAG()) == 0) { + VALUE path; + long handle; + int found; + + ruby_safe_level = safe; + FilePathValue(fname); + *(volatile VALUE *)&fname = rb_str_new4(fname); + found = search_required(fname, &path); + if (found) { + if (!path || load_wait(RSTRING(path)->ptr)) { + result = Qfalse; + } + else { + ruby_safe_level = 0; + switch (found) { + case 'r': + /* loading ruby library should be serialized. */ + if (!loading_tbl) { + loading_tbl = st_init_strtable(); + } + /* partial state */ + ftptr = ruby_strdup(RSTRING(path)->ptr); + st_insert(loading_tbl, (st_data_t)ftptr, (st_data_t)curr_thread); + rb_load(path, 0); + break; + + case 's': + ruby_current_node = 0; + ruby_sourcefile = rb_source_filename(RSTRING(path)->ptr); + ruby_sourceline = 0; + ruby_frame->callee = 0; + ruby_frame->this_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(path); + result = Qtrue; + } + } + } + POP_TAG(); + ruby_current_node = saved.node; + ruby_set_current_source(); + ruby_frame->this_func = saved.this_func; + ruby_frame->callee = saved.callee; + SCOPE_SET(saved.vmode); + ruby_safe_level = saved.safe; + if (ftptr) { + if (st_delete(loading_tbl, (st_data_t *)&ftptr, 0)) { /* loading done */ + free(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); +} + +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<argc; i++) { + rb_export_method(self, rb_to_id(argv[i]), ex); + } + rb_clear_cache_by_class(self); +} + +/* + * call-seq: + * public => 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 <code>new</code>. + * + * 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; i<argc; i++) { + VALUE m = module; + + id = rb_to_id(argv[i]); + for (;;) { + body = search_method(m, id, &m); + if (body == 0) { + body = search_method(rb_cObject, id, &m); + } + if (body == 0 || body->nd_body == 0) { + rb_bug("undefined method `%s'; can't happen", rb_id2name(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 + * <code>append_features</code> 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 <code>Module#include</code>. + */ + +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 <code>Module.append_features</code> 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<argc; i++) Check_Type(argv[i], T_MODULE); + while (argc--) { + rb_funcall(argv[argc], rb_intern("append_features"), 1, module); + rb_funcall(argv[argc], rb_intern("included"), 1, module); + } + return module; +} + +void +rb_obj_call_init(obj, argc, argv) + VALUE obj; + int argc; + VALUE *argv; +{ + PUSH_ITER(rb_block_given_p()?ITER_PRE:ITER_NOT); + rb_funcall2(obj, init, argc, argv); + POP_ITER(); +} + +void +rb_extend_object(obj, module) + VALUE obj, module; +{ + rb_include_module(rb_singleton_class(obj), module); +} + +/* + * call-seq: + * extend_object(obj) => 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 <code>Object#extend</code>. + * + * 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 + * + * <em>produces:</em> + * + * 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) #=> #<Klass:0x401b3bc8> + * 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<argc; i++) Check_Type(argv[i], T_MODULE); + while (argc--) { + rb_funcall(argv[argc], rb_intern("extend_object"), 1, obj); + rb_funcall(argv[argc], rb_intern("extended"), 1, obj); + } + return obj; +} + +/* + * call-seq: + * include(module, ...) => self + * + * Invokes <code>Module.append_features</code> + * 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; i<n; i++) { /* skip first 2 ($_ and $~) */ + if (!rb_is_local_id(tbl[i])) continue; /* skip flip states */ + rb_ary_push(ary, rb_str_new2(rb_id2name(tbl[i]))); + } + } + + vars = ruby_dyna_vars; + while (vars) { + if (vars->id && 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->callee = 0; + ruby_frame->this_func = 0; + ruby_frame->this_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 + * + * <em>produces:</em> + * + * 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((VALUE*)&top_scope); + rb_global_variable((VALUE*)&ruby_eval_tree); + rb_global_variable((VALUE*)&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?", rb_obj_respond_to, -1); + respond_to = rb_intern("respond_to?"); + basic_respond_to = rb_method_node(rb_cObject, respond_to); + rb_global_variable((VALUE*)&basic_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 <code>Kernel::require</code>) + * the first time that _module_ (which may be a <code>String</code> 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; +} + +/* + * MISSING: documentation + */ + +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 <code>Kernel::require</code>) + * the first time that _module_ (which may be a <code>String</code> 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; +{ + return rb_mod_autoload(ruby_cbase, sym, file); +} + + +/* + * MISSING: documentation + */ + +static VALUE +rb_f_autoload_p(obj, sym) + VALUE obj; + VALUE sym; +{ + /* use ruby_cbase as same as rb_f_autoload. */ + return rb_mod_autoload_p(ruby_cbase, sym); +} + +void +Init_load() +{ + rb_load_path = rb_ary_new(); + 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_features = rb_ary_new(); + rb_define_readonly_variable("$\"", &rb_features); + rb_define_readonly_variable("$LOADED_FEATURES", &rb_features); + + 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); + + ruby_dln_librefs = rb_ary_new(); + rb_global_variable(&ruby_dln_librefs); +} + +static void +scope_dup(scope) + struct SCOPE *scope; +{ + volatile 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 + */ + +static VALUE +proc_dup(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); + blk_dup(data, orig); + + 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.callee = ruby_frame->prev->callee; + data->frame.this_func = ruby_frame->prev->this_func; + data->frame.this_class = ruby_frame->prev->this_class; + } + + 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; +} + +/* + * call-seq: + * binding.eval(string [, filename [,lineno]]) => obj + * + * Evaluates the Ruby expression(s) in <em>string</em>, in the + * <em>binding</em>'s context. If the optional <em>filename</em> and + * <em>lineno</em> parameters are present, they will be used when + * reporting syntax errors. + * + * def getBinding(param) + * return binding + * end + * b = getBinding("hello") + * b.eval("param") #=> "hello" + */ + +static VALUE +bind_eval(argc, argv, bind) + int argc; + VALUE *argv; + VALUE bind; +{ + struct BLOCK *data; + VALUE args[4]; + + rb_scan_args(argc, argv, "12", &args[0], &args[2], &args[3]); + args[1] = bind; + Data_Get_Struct(bind, struct BLOCK, data); + + return rb_f_eval(argc+1, args, data->self); +} + +#define PROC_TSHIFT (FL_USHIFT+1) +#define PROC_TMASK (FL_USER1|FL_USER2|FL_USER3) +#define PROC_TMAX (PROC_TMASK >> PROC_TSHIFT) +#define PROC_NOSAFE FL_USER4 + +#define SAFE_LEVEL_MAX PROC_TMASK + +#define proc_safe_level_p(data) (!(RBASIC(data)->flags & PROC_NOSAFE)) + +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; +{ + if (!proc_safe_level_p(data)) return; + 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) { + VALUE obj = ruby_block->block_obj; + if (CLASS_OF(obj) != klass) { + obj = proc_clone(obj); + RBASIC(obj)->klass = klass; + } + return 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 <code>Proc</code> object, bound to the current + * context. <code>Proc::new</code> may be called without a block only + * within a method with an attached block, in which case that block is + * converted to the <code>Proc</code> 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; +} + +/* + * call-seq: + * proc { |...| block } => a_proc + * + * Equivalent to <code>Proc.new</code>. + */ + +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: + * lambda { |...| block } => a_proc + * + * Equivalent to <code>Proc.new</code>, 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; + VALUE bvar = Qnil, tmp = args; + + 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]; + } + if (rb_block_given_p() && ruby_frame->callee) { + if (klass != ruby_frame->this_class) + klass = rb_obj_class(proc); + bvar = rb_block_proc(); + } + + PUSH_VARS(); + ruby_wrapper = data->wrapper; + ruby_dyna_vars = data->dyna_vars; + /* PUSH BLOCK from data */ + old_block = ruby_block; + _block = *data; + _block.block_obj = bvar; + if (self != Qundef) _block.frame.self = self; + if (klass) _block.frame.this_class = klass; + _block.frame.argc = RARRAY(tmp)->len; + if (_block.frame.argc && (ruby_frame->flags & FRAME_DMETH)) { + NEWOBJ(scope, struct SCOPE); + OBJSETUP(scope, tmp, T_SCOPE); + scope->local_tbl = _block.scope->local_tbl; + scope->local_vars = _block.scope->local_vars; + _block.scope = scope; + } + ruby_block = &_block; + + PUSH_ITER(ITER_CUR); + ruby_frame->iter = ITER_CUR; + PUSH_TAG((pcall&YIELD_LAMBDA_CALL) ? 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 | YIELD_PROC_CALL, avalue); + } + else if (TAG_DST()) { + result = prot_tag->retval; + } + POP_TAG(); + POP_ITER(); + ruby_block = old_block; + ruby_wrapper = old_wrapper; + POP_VARS(); + if (proc_safe_level_p(proc)) 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_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 + * <i>params</i> 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 <code>Kernel.proc</code>, generates an + * error if the wrong number of parameters + * are passed to a proc with multiple parameters. For procs created using + * <code>Proc.new</code>, extra parameters are silently discarded. + * + * Returns the value of the last expression evaluated in the block. See + * also <code>Proc#yield</code>. + * + * 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) + * + * <em>produces:</em> + * + * 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); +} + +int +rb_proc_arity(proc) + VALUE proc; +{ + struct BLOCK *data; + NODE *var, *list; + int n; + + Data_Get_Struct(proc, struct BLOCK, data); + var = data->var; + if (var == 0) { + if (data->body && nd_type(data->body) == NODE_IFUNC && + data->body->nd_cfnc == bmcall) { + return method_arity(data->body->nd_tval); + } + return 0; + } + if (var == (NODE*)1) return 0; + if (var == (NODE*)2) return 0; + if (nd_type(var) == NODE_BLOCK_ARG) { + var = var->nd_args; + if (var == (NODE*)1) return 0; + if (var == (NODE*)2) return 0; + } + switch (nd_type(var)) { + default: + return 1; + case NODE_MASGN: + list = var->nd_head; + n = 0; + while (list) { + n++; + list = list->nd_next; + } + if (var->nd_args) return -n-1; + return n; + } +} + +/* + * 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 <code>proc</code> with no argument declarations + * is the same a block declaring <code>||</code> 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; +{ + int arity = rb_proc_arity(proc); + return INT2FIX(arity); +} + +/* + * call-seq: + * prc == other_proc => true or false + * + * Return <code>true</code> if <i>prc</i> is the same object as + * <i>other_proc</i>, 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.hash => integer + * + * Return hash value corresponding to proc body. + */ + +static VALUE +proc_hash(self) + VALUE self; +{ + struct BLOCK *data; + long hash; + + Data_Get_Struct(self, struct BLOCK, data); + hash = (long)data->body; + hash ^= (long)data->var; + hash ^= data->frame.uniq << 16; + hash ^= data->flags; + + return INT2FIX(hash); +} + +/* + * 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_LONG * 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); + sprintf(RSTRING(str)->ptr, "#<%s:0x%.*lx@%s:%d>", cname, w, (VALUE)data->body, + node->nd_file, nd_line(node)); + } + else { + str = rb_str_new(0, len); + sprintf(RSTRING(str)->ptr, "#<%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 <code>Proc</code> + * objects. Instances of class <code>Proc</code> simply return + * themselves. + */ + +static VALUE +proc_to_self(self) + VALUE self; +{ + return self; +} + +/* + * call-seq: + * prc.binding => binding + * + * Returns the binding associated with <i>prc</i>. Note that + * <code>Kernel#eval</code> accepts either a <code>Proc</code> or a + * <code>Binding</code> 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 +rb_block_pass(func, arg, proc) + VALUE (*func) _((VALUE)); + VALUE arg; + VALUE proc; +{ + 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 = (*func)(arg); + 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)) { + if (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_PRE); + result = (*func)(arg); + POP_ITER(); + return result; + } + + Data_Get_Struct(proc, struct BLOCK, data); + orphan = block_orphan(data); + + /* PUSH BLOCK from data */ + _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 = (*func)(arg); + } + 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 = _block.outer; + if (proc_safe_level_p(proc)) 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 block_arg { + VALUE self; + NODE *iter; +}; + +static VALUE +call_block(arg) + struct block_arg *arg; +{ + return rb_eval(arg->self, arg->iter); +} + +static VALUE +block_pass(self, node) + VALUE self; + NODE *node; +{ + struct block_arg arg; + arg.self = self; + arg.iter = node->nd_iter; + return rb_block_pass((VALUE (*)_((VALUE)))call_block, + (VALUE)&arg, rb_eval(self, node->nd_body)); +} + +struct METHOD { + VALUE klass, rklass; + VALUE recv; + ID id, oid; + 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; + } + if (TYPE(klass) == T_ICLASS) klass = RBASIC(klass)->klass; + method = Data_Make_Struct(mklass, struct METHOD, bm_mark, -1, data); + data->klass = klass; + data->recv = obj; + data->id = id; + data->body = body; + data->rklass = rklass; + data->oid = oid; + OBJ_INFECT(method, klass); + + return method; +} + + +/********************************************************************** + * + * Document-class : Method + * + * Method objects are created by <code>Object#method</code>, 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 <code>UnboundMethod</code>) 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.hash => integer + * + * Return a hash value corresponding to the method object. + */ + +static VALUE +method_hash(method) + VALUE method; +{ + struct METHOD *m; + long hash; + + Data_Get_Struct(method, struct METHOD, m); + hash = (long)m->klass; + hash ^= (long)m->rklass; + hash ^= (long)m->recv; + hash ^= (long)m->body; + + return INT2FIX(hash); +} + +/* + * call-seq: + * meth.unbind => unbound_method + * + * Dissociates <i>meth</i> from it's current receiver. The resulting + * <code>UnboundMethod</code> can subsequently be bound to a new object + * of the same class (see <code>UnboundMethod</code>). + */ + +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 <i>obj</i>, returning a + * <code>Method</code> object (or raising <code>NameError</code>). The + * <code>Method</code> object acts as a closure in <i>obj</i>'s object + * instance, so instance variables and the value of <code>self</code> + * 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') + * + * <em>produces:</em> + * + * 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 <i>meth</i> 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 state; + volatile int safe = -1; + + Data_Get_Struct(method, struct METHOD, data); + if (data->recv == Qundef) { + rb_raise(rb_eTypeError, "can't call unbound method; bind first"); + } + PUSH_ITER(rb_block_given_p()?ITER_PRE:ITER_NOT); + PUSH_TAG(PROT_NONE); + if (OBJ_TAINTED(method)) { + safe = ruby_safe_level; + if (ruby_safe_level < 4) ruby_safe_level = 4; + } + if ((state = EXEC_TAG()) == 0) { + result = rb_call0(data->klass,data->recv,data->id,data->oid,argc,argv,data->body,0); + } + POP_TAG(); + POP_ITER(); + if (safe >= 0) ruby_safe_level = safe; + if (state) JUMP_TAG(state); + return result; +} + +/********************************************************************** + * + * Document-class: UnboundMethod + * + * Ruby supports two forms of objectified methods. Class + * <code>Method</code> 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 + * <code>Object#method</code>. + * + * Ruby also supports unbound methods; methods objects that are not + * associated with a particular object. These can be created either by + * calling <code>Module#instance_method</code> or by calling + * <code>unbind</code> on a bound method object. The result of both of + * these is an <code>UnboundMethod</code> 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 <i>umeth</i> to <i>obj</i>. If <code>Klass</code> was the class + * from which <i>umeth</i> was obtained, + * <code>obj.kind_of?(Klass)</code> 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 + * + * <em>produces:</em> + * + * 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; + + Data_Get_Struct(method, struct METHOD, data); + if (data->rklass != CLASS_OF(recv)) { + if (FL_TEST(data->rklass, FL_SINGLETON)) { + rb_raise(rb_eTypeError, "singleton method called for a different object"); + } + if(!rb_obj_is_kind_of(recv, data->rklass)) { + 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 = CLASS_OF(recv); + + return method; +} + +int +rb_node_arity(body) + NODE *body; +{ + int n; + + switch (nd_type(body)) { + case NODE_CFUNC: + if (body->nd_argc < 0) return -1; + return body->nd_argc; + case NODE_ZSUPER: + return -1; + case NODE_ATTRSET: + return 1; + case NODE_IVAR: + return 0; + case NODE_BMETHOD: + return rb_proc_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 0; + n = body->nd_cnt; + if (body->nd_opt || body->nd_rest != -1) + n = -n-1; + return n; + default: + rb_raise(rb_eArgError, "invalid node 0x%x", nd_type(body)); + } +} + +/* + * 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_m(method) + VALUE method; +{ + int n = method_arity(method); + return INT2FIX(n); +} + +static int +method_arity(method) + VALUE method; +{ + struct METHOD *data; + + Data_Get_Struct(method, struct METHOD, data); + return rb_node_arity(data->body); +} + +int +rb_mod_method_arity(mod, id) + VALUE mod; + ID id; +{ + NODE *node = rb_method_node(mod, id); + return rb_node_arity(node); +} + +int +rb_obj_method_arity(obj, id) + VALUE obj; + ID id; +{ + return rb_mod_method_arity(CLASS_OF(obj), id); +} + +/* + * call-seq: + * meth.to_s => string + * meth.inspect => string + * + * Show the name of the underlying method. + * + * "cat".method(:count).inspect #=> "#<Method: String#count>" + */ + +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) { + rb_str_buf_cat2(str, "("); + rb_str_buf_cat2(str, rb_class2name(data->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; + + a = svalue_to_avalue(args); + return method_call(RARRAY(a)->len, RARRAY(a)->ptr, method); +} + +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 <code>Proc</code> object corresponding to this method. + */ + +static VALUE +method_proc(method) + VALUE method; +{ + VALUE proc; + struct METHOD *mdata; + struct BLOCK *bdata; + + Data_Get_Struct(method, struct METHOD, mdata); + if (nd_type(mdata->body) == NODE_BMETHOD) { + return mdata->body->nd_cval; + } + proc = rb_iterate((VALUE(*)_((VALUE)))mproc, 0, bmcall, method); + 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; + bdata->flags |= BLOCK_FROM_METHOD; + + 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 <code>instance_eval</code>, a point that is + * tricky to demonstrate because <code>define_method</code> 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 + * + * <em>produces:</em> + * + * In Fred + * Charge it! + * #<B:0x401b39e8> + */ + +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) { + struct METHOD *method = (struct METHOD *)DATA_PTR(body); + VALUE rklass = method->rklass; + if (rklass != mod) { + if (FL_TEST(rklass, FL_SINGLETON)) { + rb_raise(rb_eTypeError, "can't bind singleton method to a different class"); + } + if (!RTEST(rb_class_inherited_p(mod, rklass))) { + rb_raise(rb_eTypeError, "bind argument must be a subclass of %s", + rb_class2name(rklass)); + } + } + node = method->body; + } + else if (RDATA(body)->dmark == (RUBY_DATA_FUNC)blk_mark) { + struct BLOCK *block; + + body = proc_clone(body); + RBASIC(body)->flags |= PROC_NOSAFE; + Data_Get_Struct(body, struct BLOCK, block); + block->frame.callee = id; + block->frame.this_func = id; + block->frame.this_class = mod; + node = NEW_BMETHOD(body); + } + else { + /* type error */ + rb_raise(rb_eTypeError, "wrong argument type (expected Proc/Method)"); + } + + if (SCOPE_TEST(SCOPE_PRIVATE)) { + noex = NOEX_PRIVATE; + } + else if (SCOPE_TEST(SCOPE_PROTECTED)) { + noex = NOEX_PROTECTED; + } + else { + noex = NOEX_PUBLIC; + } + rb_add_method(mod, id, node, noex); + return body; +} + +/* + * <code>Proc</code> 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); + + exception_error = rb_exc_new2(rb_eFatal, "exception reentered"); + rb_global_variable(&exception_error); + + rb_eSysStackError = rb_define_class("SystemStackError", rb_eException); + sysstack_error = rb_exc_new2(rb_eSysStackError, "stack level too deep"); + OBJ_TAINT(sysstack_error); + rb_global_variable(&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, "eql?", proc_eq, 1); + rb_define_method(rb_cProc, "hash", proc_hash, 0); + 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", rb_block_proc, 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, "eql?", method_eq, 1); + rb_define_method(rb_cMethod, "hash", method_hash, 0); + 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_m, 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, "eql?", method_eq, 1); + rb_define_method(rb_cUnboundMethod, "hash", method_hash, 0); + rb_define_method(rb_cUnboundMethod, "clone", method_clone, 0); + rb_define_method(rb_cUnboundMethod, "arity", method_arity_m, 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 <code>Binding</code> encapsulate the execution + * context at some particular place in the code and retain this context + * for future use. The variables, methods, value of <code>self</code>, + * and possibly an iterator block that can be accessed in this context + * are all retained. Binding objects can be created using + * <code>Kernel#binding</code>, and are made available to the callback + * of <code>Kernel#set_trace_func</code>. + * + * These binding objects can be passed as the second argument of the + * <code>Kernel#eval</code> 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, "eval", bind_eval, -1); + rb_define_global_function("binding", rb_f_binding, 0); +} + +#ifdef __ia64__ +#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. + */ +#define __libc_ia64_register_backing_store_base (4ULL<<61) +#else +#ifdef HAVE_UNWIND_H +#include <unwind.h> +#else +#pragma weak __libc_ia64_register_backing_store_base +extern unsigned long __libc_ia64_register_backing_store_base; +#endif +#endif +#endif + +/* 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; + +enum thread_status { + THREAD_TO_KILL, + THREAD_RUNNABLE, + THREAD_STOPPED, + THREAD_KILLED, +}; + +#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 + +/* typedef struct thread * rb_thread_t; */ + +struct thread { + struct thread *next, *prev; + rb_jmpbuf_t context; +#ifdef SAVE_WIN32_EXCEPTION_LIST + DWORD win32_exception_list; +#endif + + VALUE result; + + long stk_len; + long stk_max; + VALUE *stk_ptr; + VALUE *stk_pos; +#ifdef __ia64__ + VALUE *bstr_ptr; + long bstr_len; +#endif + + struct FRAME *frame; + struct SCOPE *scope; + struct RVarmap *dyna_vars; + struct BLOCK *block; + struct iter *iter; + struct tag *tag; + VALUE klass; + VALUE wrapper; + NODE *cref; + struct ruby_env *anchor; + + int flags; /* misc. states (vmode/rb_trap_immediate/raised) */ + + NODE *node; + + int tracing; + VALUE errinfo; + VALUE last_status; + VALUE last_line; + VALUE last_match; + + int safe; + + enum thread_status status; + int wait_for; + int fd; + fd_set readfds; + fd_set writefds; + fd_set exceptfds; + int select_value; + double delay; + rb_thread_t join; + + int abort; + int priority; + VALUE thgroup; + + st_table *locals; + + VALUE thread; +}; + +#define THREAD_RAISED 0x200 /* temporary flag */ +#define THREAD_TERMINATING 0x400 /* persistent flag */ +#define THREAD_FLAGS_MASK 0x400 /* 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 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) + +static int +thread_set_raised() +{ + if (curr_thread->flags & THREAD_RAISED) return 1; + curr_thread->flags |= THREAD_RAISED; + return 0; +} + +static int +thread_reset_raised() +{ + if (!(curr_thread->flags & THREAD_RAISED)) return 0; + curr_thread->flags &= ~THREAD_RAISED; + return 1; +} + +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; + JUMP_TAG(state); + } + + if (curr_thread->status == THREAD_STOPPED) { + rb_thread_schedule(); + } + errno = EINTR; + + return val; +} + +static const char * +thread_status_name(status) + enum 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; + gettimeofday(&tv, NULL); + return (double)tv.tv_sec + (double)tv.tv_usec * 1e-6; +} + +#define STACK(addr) (th->stk_pos<(VALUE*)(addr) && (VALUE*)(addr)<th->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_line); + rb_gc_mark(th->last_match); + rb_mark_tbl(th->locals); + rb_gc_mark(th->thgroup); + + /* 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 struct { + rb_thread_t thread; + VALUE proc, arg; +} new_thread; + +void +rb_gc_mark_threads() +{ + rb_thread_t th; + + /* static global mark */ + rb_gc_mark((VALUE)ruby_cref); + + if (!curr_thread) return; + FOREACH_THREAD(th) { + rb_gc_mark(th->thread); + } END_FOREACH(th); + if (new_thread.thread) { + rb_gc_mark(new_thread.thread->thread); + rb_gc_mark(new_thread.proc); + rb_gc_mark(new_thread.arg); + } +} + +static void +thread_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 + 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; +static char *th_signm; + +#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; + +static void +rb_thread_save_context(th) + rb_thread_t th; +{ + VALUE *pos; + int len; + static VALUE tval; + + len = ruby_stack_length(&pos); + th->stk_len = 0; + th->stk_pos = pos; + if (len > th->stk_max) { + REALLOC_N(th->stk_ptr, VALUE, len); + th->stk_max = len; + } + th->stk_len = len; + FLUSH_REGISTER_WINDOWS; + MEMCPY(th->stk_ptr, th->stk_pos, VALUE, th->stk_len); +#ifdef __ia64__ + { + VALUE *top, *bot; +#ifdef HAVE_UNWIND_H + _Unwind_Context *unwctx = _UNW_createContextForSelf(); + + _UNW_currentContext(unwctx); + bot = (VALUE*)(long)_UNW_getAR(unwctx, _UNW_AR_BSP); + top = (VALUE*)(long)_UNW_getAR(unwctx, _UNW_AR_BSPSTORE); + _UNW_destroyContext(unwctx); +#else + ucontext_t ctx; + + getcontext(&ctx); + bot = (VALUE*)__libc_ia64_register_backing_store_base; + top = (VALUE*)ctx.uc_mcontext.IA64_BSPSTORE; +#endif + th->bstr_len = top - bot; + REALLOC_N(th->bstr_ptr, VALUE, th->bstr_len); + MEMCPY(th->bstr_ptr, bot, 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; + 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; +} + +static int +rb_thread_switch(n) + int n; +{ + rb_trap_immediate = (curr_thread->flags&(1<<8))?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->callee = 0; + ruby_frame->this_func = 0; + ruby_current_node = th_raise_node; + rb_raise_jump(th_raise_exception); + break; + case RESTORE_SIGNAL: + rb_raise(rb_eSignal, "SIG%s", th_signm); + break; + case RESTORE_EXIT: + ruby_errinfo = th_raise_exception; + ruby_current_node = th_raise_node; + error_print(); + terminate_process(EXIT_FAILURE, 0, 0); + break; + case RESTORE_NORMAL: + default: + break; + } + return 1; +} + +#define THREAD_SAVE_CONTEXT(th) \ + (rb_thread_save_context(th),\ + rb_thread_switch((FLUSH_REGISTER_WINDOWS, setjmp((th)->context)))) + +NORETURN(static void rb_thread_restore_context _((rb_thread_t,int))); +NOINLINE(static void stack_extend _((rb_thread_t, int))); + +static void +stack_extend(th, exit) + rb_thread_t th; + int exit; +{ + VALUE space[1024]; + + memset(space, 0, 1); /* prevent array from optimization */ + rb_thread_restore_context(th, exit); +} + +static void +rb_thread_restore_context(th, exit) + rb_thread_t th; + int exit; +{ + VALUE v; + static rb_thread_t tmp; + static int ex; + static VALUE tval; + + if (!th->stk_ptr) rb_bug("unsaved context"); + +#if STACK_GROW_DIRECTION < 0 + if (&v > th->stk_pos) stack_extend(th, exit); +#elif STACK_GROW_DIRECTION > 0 + if (&v < th->stk_pos + th->stk_len) stack_extend(th, exit); +#else + if (&v < rb_gc_stack_start) { + /* Stack grows downward */ + if (&v > th->stk_pos) stack_extend(th, exit); + } + else { + /* Stack grows upward */ + if (&v < th->stk_pos + th->stk_len) stack_extend(th, exit); + } +#endif + + rb_trap_immediate = 0; /* inhibit interrupts from here */ + 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__ + { + VALUE *base; +#ifdef HAVE_UNWIND_H + _Unwind_Context *unwctx = _UNW_createContextForSelf(); + + _UNW_currentContext(unwctx); + base = (VALUE*)(long)_UNW_getAR(unwctx, _UNW_AR_BSP); + _UNW_destroyContext(unwctx); +#else + base = (VALUE*)__libc_ia64_register_backing_store_base; +#endif + MEMCPY(base, 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; + + longjmp(tmp->context, ex); +} + +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; + if (th->stk_ptr) free(th->stk_ptr); + th->stk_ptr = 0; +} + +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; + 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; + 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 + 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. + Make the corresponding threads runnable. */ + FOREACH_THREAD_FROM(curr, th) { + if ((th->wait_for&WAIT_FD) && FD_ISSET(th->fd, &readfds)) { + /* Wake up only one thread per fd. */ + FD_CLR(th->fd, &readfds); + th->status = THREAD_RUNNABLE; + th->fd = 0; + th->wait_for = 0; + found = 1; + } + if ((th->wait_for&WAIT_SELECT) && + (match_fds(&readfds, &th->readfds, max) || + match_fds(&writefds, &th->writefds, max) || + match_fds(&exceptfds, &th->exceptfds, max))) { + /* Wake up only one thread per fd. */ + th->status = THREAD_RUNNABLE; + th->wait_for = 0; + n = intersect_fds(&readfds, &th->readfds, max) + + intersect_fds(&writefds, &th->writefds, max) + + intersect_fds(&exceptfds, &th->exceptfds, max); + th->select_value = n; + found = 1; + } + } + 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->stk_ptr) { + if (!next || next->priority < th->priority) + 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 (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; + + 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 + return; + 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; +{ + double limit; + int n; + + if (!read && !write && !except) { + if (!timeout) { + rb_thread_sleep_forever(); + return 0; + } + rb_thread_wait_for(*timeout); + return 0; + } + + if (timeout) { + limit = timeofday()+ + (double)timeout->tv_sec+(double)timeout->tv_usec*1e-6; + } + + 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; + return curr_thread->select_value; +} + +static int rb_thread_join _((rb_thread_t, double)); + +static int +rb_thread_join(th, limit) + rb_thread_t th; + double limit; +{ + enum 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 & THREAD_RAISED)) { + 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; +} + + +/* + * call-seq: + * thr.join => thr + * thr.join(limit) => thr + * + * The calling thread will suspend execution and run <i>thr</i>. Does not + * return until <i>thr</i> exits or until <i>limit</i> seconds have passed. If + * the time limit expires, <code>nil</code> will be returned, otherwise + * <i>thr</i> is returned. + * + * Any threads not joined will be killed when the main program exits. If + * <i>thr</i> had previously raised an exception and the + * <code>abort_on_exception</code> and <code>$DEBUG</code> 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. + * + * <em>produces:</em> + * + * axyz + * + * The following example illustrates the <i>limit</i> parameter. + * + * y = Thread.new { 4.times { sleep 0.1; puts 'tick... ' }} + * puts "Waiting" until y.join(0.15) + * + * <em>produces:</em> + * + * 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_thread_t th = rb_thread_check(thread); + + rb_scan_args(argc, argv, "01", &limit); + if (!NIL_P(limit)) delay = rb_num2dbl(limit); + if (!rb_thread_join(th, delay)) + return Qnil; + return thread; +} + + +/* + * call-seq: + * Thread.current => thread + * + * Returns the currently executing thread. + * + * Thread.current #=> #<Thread:0x401bdf4c run> + */ + +VALUE +rb_thread_current() +{ + return curr_thread->thread; +} + + +/* + * call-seq: + * Thread.main => thread + * + * Returns the main thread for the process. + * + * Thread.main #=> #<Thread:0x401bdf4c run> + */ + +VALUE +rb_thread_main() +{ + return main_thread->thread; +} + + +/* + * call-seq: + * Thread.list => array + * + * Returns an array of <code>Thread</code> 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} + * + * <em>produces:</em> + * + * #<Thread:0x401b3e84 sleep> + * #<Thread:0x401b3f38 run> + * #<Thread:0x401b3fb0 sleep> + * #<Thread:0x401bdf4c run> + */ + +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 <i>thr</i> as eligible for scheduling (it may still remain blocked on + * I/O, however). Does not invoke the scheduler (see <code>Thread#run</code>). + * + * c = Thread.new { Thread.stop; puts "hey!" } + * c.wakeup + * + * <em>produces:</em> + * + * hey! + */ + +VALUE +rb_thread_wakeup(thread) + VALUE thread; +{ + rb_thread_t th = rb_thread_check(thread); + + if (th->status == THREAD_KILLED) + rb_raise(rb_eThreadError, "killed thread"); + rb_thread_ready(th); + + return thread; +} + + +/* + * call-seq: + * thr.run => thr + * + * Wakes up <i>thr</i>, 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 + * + * <em>produces:</em> + * + * a + * Got here + * c + */ + +VALUE +rb_thread_run(thread) + VALUE thread; +{ + rb_thread_wakeup(thread); + if (!rb_thread_critical) rb_thread_schedule(); + + return thread; +} + + +/* + * call-seq: + * thr.exit => thr or nil + * thr.kill => thr or nil + * thr.terminate => thr or nil + * + * Terminates <i>thr</i> and schedules another thread to be run. If this thread + * is already marked to be killed, <code>exit</code> returns the + * <code>Thread</code>. 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); + + if (th != curr_thread && th->safe < 4) { + rb_secure(4); + } + if (th->status == THREAD_TO_KILL || th->status == THREAD_KILLED) + return thread; + if (th == th->next || th == main_thread) rb_exit(EXIT_SUCCESS); + + rb_thread_ready(th); + th->status = THREAD_TO_KILL; + if (!rb_thread_critical) rb_thread_schedule(); + return thread; +} + + +/* + * call-seq: + * Thread.kill(thread) => thread + * + * Causes the given <em>thread</em> to exit (see <code>Thread::exit</code>). + * + * count = 0 + * a = Thread.new { loop { count += 1 } } + * sleep(0.1) #=> 0 + * Thread.kill(a) #=> #<Thread:0x401b3d30 dead> + * 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, <code>exit</code> + * returns the <code>Thread</code>. 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 + * + * <em>produces:</em> + * + * 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 <code>false</code>. + * + * a = Thread.new { print "a"; Thread.stop; print "c" } + * Thread.pass + * print "b" + * a.run + * a.join + * + * <em>produces:</em> + * + * abc + */ + +VALUE +rb_thread_stop() +{ + enum 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 <i>thr</i>. Default is zero; 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 <i>thr</i> to <i>integer</i>. 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 <i>thr</i>. 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 <code>false</code>. When set to <code>true</code>, or if the + * global <code>$DEBUG</code> flag is <code>true</code> (perhaps because the + * command line option <code>-d</code> was specified) all threads will abort + * (the process will <code>exit(0)</code>) if an exception is raised in any + * thread. See also <code>Thread::abort_on_exception=</code>. + */ + +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 <code>true</code>, 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" + * + * <em>produces:</em> + * + * 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 + * <i>thr</i>. The default is <code>false</code>. See also + * <code>Thread::abort_on_exception=</code>. + */ + +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 <code>true</code>, causes all threads (including the main + * program) to abort if an exception is raised in <i>thr</i>. The process will + * effectively <code>exit(0)</code>. + */ + +static VALUE +rb_thread_abort_exc_set(thread, val) + VALUE thread, val; +{ + rb_secure(4); + rb_thread_check(thread)->abort = RTEST(val); + return val; +} + + +/* + * call-seq: + * thr.group => thgrp or nil + * + * Returns the <code>ThreadGroup</code> which contains <i>thr</i>, or nil if + * the thread is not a member of any group. + * + * Thread.main.group #=> #<ThreadGroup:0x4029d914> + */ + +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 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);\ + 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;\ + th->anchor = 0;\ +} 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 = 0; + +#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 pthread_t time_thread; + +static void* +thread_timer(dummy) + void *dummy; +{ + for (;;) { +#ifdef HAVE_NANOSLEEP + struct timespec req, rem; + + req.tv_sec = 0; + req.tv_nsec = 10000000; + nanosleep(&req, &rem); +#else + struct timeval tv; + 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); + } + } + } +} + +void +rb_thread_start_timer() +{ +} + +void +rb_thread_stop_timer() +{ +} +#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); +} +#else /* !(_THREAD_SAFE || HAVE_SETITIMER) */ +int rb_thread_tick = THREAD_TICK; +#endif + +NORETURN(static void rb_thread_terminated _((rb_thread_t, int, enum thread_status))); +static VALUE rb_thread_yield _((VALUE, rb_thread_t)); + +static void +push_thread_anchor(ip) + struct ruby_env *ip; +{ + ip->tag = prot_tag; + ip->frame = ruby_frame; + ip->block = ruby_block; + ip->scope = ruby_scope; + ip->iter = ruby_iter; + ip->cref = ruby_cref; + ip->prev = curr_thread->anchor; + curr_thread->anchor = ip; +} + +static void +pop_thread_anchor(ip) + struct ruby_env *ip; +{ + curr_thread->anchor = ip->prev; +} + +static void +thread_insert(th) + rb_thread_t th; +{ + 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; + } +} + +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 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); +#else + rb_thread_start_timer(); +#endif +#endif + } + + if (THREAD_SAVE_CONTEXT(curr_thread)) { + return thread; + } + + if (fn == rb_thread_yield && curr_thread->anchor) { + struct ruby_env *ip = curr_thread->anchor; + new_thread.thread = th; + new_thread.proc = rb_block_proc(); + new_thread.arg = (VALUE)arg; + th->anchor = ip; + thread_insert(th); + curr_thread = th; + longjmp((prot_tag = ip->tag)->buf, TAG_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); + + thread_insert(th); + + PUSH_TAG(PROT_NONE); + 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); + } + + rb_thread_terminated(th, state, status); + return 0; /* not reached */ +} + +static void +rb_thread_terminated(th, state, status) + rb_thread_t th; + int state; + enum thread_status status; +{ + if (state && status != THREAD_TO_KILL && !NIL_P(ruby_errinfo)) { + th->flags |= THREAD_RAISED; + 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 */ + rb_thread_main_jump(ruby_errinfo, RESTORE_EXIT); + } + else { + th->errinfo = ruby_errinfo; + } + } + rb_thread_schedule(); + ruby_stop(0); /* last thread termination */ +} + +static VALUE +rb_thread_yield_0(arg) + VALUE arg; +{ + return rb_thread_yield(arg, curr_thread); +} + +static void +rb_thread_start_1() +{ + rb_thread_t th = new_thread.thread; + volatile rb_thread_t th_save = th; + VALUE proc = new_thread.proc; + VALUE arg = new_thread.arg; + struct ruby_env *ip = th->anchor; + enum thread_status status; + int state; + + ruby_frame = ip->frame; + ruby_block = ip->block; + ruby_scope = ip->scope; + ruby_iter = ip->iter; + ruby_cref = ip->cref; + ruby_dyna_vars = ((struct BLOCK *)DATA_PTR(proc))->dyna_vars; + PUSH_FRAME(); + *ruby_frame = *ip->frame; + ruby_frame->prev = ip->frame; + ruby_frame->iter = ITER_CUR; + PUSH_TAG(PROT_NONE); + if ((state = EXEC_TAG()) == 0) { + if (THREAD_SAVE_CONTEXT(th) == 0) { + new_thread.thread = 0; + th->result = rb_block_pass(rb_thread_yield_0, arg, proc); + } + th = th_save; + } + else if (TAG_DST()) { + th = th_save; + th->result = prot_tag->retval; + } + POP_TAG(); + POP_FRAME(); + status = th->status; + + if (th == main_thread) ruby_stop(state); + rb_thread_remove(th); + rb_thread_terminated(th, state, status); +} + +VALUE +rb_thread_create(fn, arg) + VALUE (*fn)(); + void *arg; +{ + Init_stack((VALUE*)&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 + * <i>block</i>. Any arguments passed to <code>Thread::new</code> 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... + * + * <em>produces:</em> + * + * 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 + * <i>block</i>. Any arguments passed to <code>Thread::new</code> 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... + * + * <em>produces:</em> + * + * 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 <code>Thread::new</code>. However, if class + * <code>Thread</code> is subclassed, then calling <code>start</code> in that + * subclass will not invoke the subclass's <code>initialize</code> 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 <i>thr</i> to complete (via <code>Thread#join</code>) 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_join(th, DELAY_INFTY)); + + return th->result; +} + + +/* + * call-seq: + * thr.status => string, false or nil + * + * Returns the status of <i>thr</i>: ``<code>sleep</code>'' if <i>thr</i> is + * sleeping or waiting on I/O, ``<code>run</code>'' if <i>thr</i> is executing, + * ``<code>aborting</code>'' if <i>thr</i> is aborting, <code>false</code> if + * <i>thr</i> terminated normally, and <code>nil</code> if <i>thr</i> + * 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 #=> #<Thread:0x401b3678 aborting> + * a.status #=> nil + * b.status #=> "sleep" + * c.status #=> false + * d.status #=> "aborting" + * Thread.current.status #=> "run" + */ + +static VALUE +rb_thread_status(thread) + VALUE thread; +{ + rb_thread_t th = rb_thread_check(thread); + + if (rb_thread_dead(th)) { + if (!NIL_P(th->errinfo) && (th->flags & THREAD_RAISED)) + return Qnil; + return Qfalse; + } + + return rb_str_new2(thread_status_name(th->status)); +} + + +/* + * call-seq: + * thr.alive? => true or false + * + * Returns <code>true</code> if <i>thr</i> is running or sleeping. + * + * thr = Thread.new { } + * thr.join #=> #<Thread:0x401b3fb0 dead> + * Thread.current.alive? #=> true + * thr.alive? #=> false + */ + +static 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 <code>true</code> if <i>thr</i> 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 <code>true</code>, 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. <code>Thread::critical</code> 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) + char *sig; +{ + if (sig == 0) return; /* should not happen */ + rb_thread_critical = 0; + if (curr_thread == main_thread) { + rb_thread_ready(curr_thread); + rb_raise(rb_eSignal, "SIG%s", sig); + } + rb_thread_ready(main_thread); + if (!rb_thread_dead(curr_thread)) { + if (THREAD_SAVE_CONTEXT(curr_thread)) { + return; + } + } + th_signm = 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); +} + +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 <code>Kernel::raise</code>) from <i>thr</i>. The + * caller does not have to be <i>thr</i>. + * + * Thread.abort_on_exception = true + * a = Thread.new { sleep(200) } + * a.raise("Gotcha") + * + * <em>produces:</em> + * + * 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 <code>nil</code>. + * + * 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]}" } + * + * <em>produces:</em> + * + * #<Thread:0x401b3b3c sleep>: C + * #<Thread:0x401b3bc8 sleep>: B + * #<Thread:0x401b3c68 sleep>: A + * #<Thread:0x401bdf4c run>: + */ + +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 <code>Thread#[]</code>. + */ + +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 <code>true</code> 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 #=> #<Thread:0x401b3f10 dead> + * 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; + + str = rb_str_new(0, strlen(cname)+7+16+9+1); /* 7:tags 16:addr 9:status 1:nul */ + sprintf(RSTRING(str)->ptr, "#<%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; +} + + +/* + * Document-class: Continuation + * + * Continuation objects are generated by + * <code>Kernel#callcc</code>. They hold a return address and execution + * context, allowing a nonlocal return to the end of the + * <code>callcc</code> block from anywhere within a program. + * Continuations are somewhat analogous to a structured version of C's + * <code>setjmp/longjmp</code> (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/ + * + * <em>produces:</em> + * + * 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" + * + * <em>produces:</em> + * + * 0: 0 1 2 3 4 + * 1: 5 6 7 8 9 + * 2: 10 11 12 13 14 + * 3: 15 16 + */ + +static VALUE rb_cCont; + +/* + * call-seq: + * callcc {|cont| block } => obj + * + * Generates a <code>Continuation</code> object, which it passes to the + * associated block. Performing a <em>cont</em><code>.call</code> will + * cause the <code>callcc</code> to return (as will falling through the + * end of the block). The value returned by the <code>callcc</code> is + * the value of the block, or the value passed to + * <em>cont</em><code>.call</code>. See class <code>Continuation</code> + * for more details. Also see <code>Kernel::throw</code> 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); + cont = Data_Wrap_Struct(rb_cCont, thread_mark, thread_free, th); + + scope_dup(ruby_scope); + for (tag=prot_tag; tag; tag=tag->prev) { + scope_dup(tag->scope); + } + th->thread = curr_thread->thread; + th->thgroup = cont_protect; + + 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 + * <code>callcc</code> block. If no arguments are given, the original + * <code>callcc</code> returns <code>nil</code>. If one argument is + * given, <code>callcc</code> returns it. Otherwise, an array + * containing <i>args</i> 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_thread_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 + * + * <code>ThreadGroup</code> provides a means of keeping track of a number of + * threads as a group. A <code>Thread</code> can belong to only one + * <code>ThreadGroup</code> 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 <code>Thread</code> objects that belong to + * this group. + * + * ThreadGroup::Default.list #=> [#<Thread:0x401bdf4c run>] + */ + +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 + * <code>ThreadGroup</code>. New threads can still be started in an enclosed + * <code>ThreadGroup</code>. + * + * ThreadGroup::Default.enclose #=> #<ThreadGroup:0x4029d914> + * thr = Thread::new { Thread.stop } #=> #<Thread:0x402a7210 sleep> + * tg = ThreadGroup::new #=> #<ThreadGroup:0x402752d4> + * tg.add thr + * + * <em>produces:</em> + * + * ThreadError: can't move from the enclosed thread group + */ + +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 <code>true</code> if <em>thgrp</em> 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 <em>thread</em> 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}" + * + * <em>produces:</em> + * + * Initial group is #<Thread:0x401bdf4c> + * t1 is #<Thread:0x401b3c90> + * t2 is #<Thread:0x401b3c18> + * Initial group now #<Thread:0x401b3c18>#<Thread:0x401bdf4c> + * tg group now #<Thread:0x401b3c90> + */ + +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 (!th->next || !th->prev) { + rb_raise(rb_eTypeError, "wrong argument type %s (expected Thread)", + rb_obj_classname(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; +} + +/* variables for recursive traversals */ +static ID recursive_key; +static VALUE recursive_tbl; + + +/* + * +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 <code>:name</code>). + */ + +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, "value", rb_thread_value, 0); + rb_define_method(rb_cThread, "status", rb_thread_status, 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); + thgroup_default = rb_obj_alloc(cThGroup); + rb_define_const(cThGroup, "Default", thgroup_default); + rb_global_variable(&thgroup_default); + + /* allocate main thread */ + main_thread = rb_thread_alloc(rb_cThread); + curr_thread = main_thread->prev = main_thread->next = main_thread; + recursive_key = rb_intern("__recursive_key__"); +} + +/* + * 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) } + * + * <em>produces:</em> + * + * 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 + * <code>Kernel::catch</code>. + */ + +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); +} + +static VALUE +recursive_check(obj) + VALUE obj; +{ + VALUE hash = rb_thread_local_aref(rb_thread_current(), recursive_key); + + if (NIL_P(hash) || TYPE(hash) != T_HASH) { + return Qfalse; + } + else { + VALUE list = rb_hash_aref(hash, ID2SYM(ruby_frame->this_func)); + + if (NIL_P(list) || TYPE(list) != T_ARRAY) return Qfalse; + return rb_ary_includes(list, rb_obj_id(obj)); + } +} + +static void +recursive_push(obj) + VALUE obj; +{ + VALUE hash = rb_thread_local_aref(rb_thread_current(), recursive_key); + VALUE list, sym; + + sym = ID2SYM(ruby_frame->this_func); + if (NIL_P(hash) || TYPE(hash) != T_HASH) { + hash = rb_hash_new(); + rb_thread_local_aset(rb_thread_current(), recursive_key, hash); + list = Qnil; + } + else { + list = rb_hash_aref(hash, sym); + } + if (NIL_P(list) || TYPE(list) != T_ARRAY) { + list = rb_ary_new(); + rb_hash_aset(hash, sym, list); + } + rb_ary_push(list, rb_obj_id(obj)); +} + +static void +recursive_pop() +{ + VALUE hash = rb_thread_local_aref(rb_thread_current(), recursive_key); + VALUE list, sym; + + sym = ID2SYM(ruby_frame->this_func); + if (NIL_P(hash) || TYPE(hash) != T_HASH) { + VALUE symname = rb_inspect(sym); + VALUE thrname = rb_inspect(rb_thread_current()); + rb_raise(rb_eTypeError, "invalid inspect_tbl hash for %s in %s", + StringValuePtr(symname), StringValuePtr(thrname)); + } + list = rb_hash_aref(hash, sym); + if (NIL_P(list) || TYPE(list) != T_ARRAY) { + VALUE symname = rb_inspect(sym); + VALUE thrname = rb_inspect(rb_thread_current()); + rb_raise(rb_eTypeError, "invalid inspect_tbl list for %s in %s", + StringValuePtr(symname), StringValuePtr(thrname)); + } + rb_ary_pop(list); +} + +VALUE +rb_exec_recursive(func, obj, arg) + VALUE (*func)(ANYARGS); /* VALUE obj, VALUE arg, int flag */ + VALUE obj, arg; +{ + if (recursive_check(obj)) { + return (*func)(obj, arg, Qtrue); + } + else { + VALUE result; + int state; + + recursive_push(obj); + PUSH_TAG(PROT_NONE); + if ((state = EXEC_TAG()) == 0) { + result = (*func)(obj, arg, Qfalse); + } + POP_TAG(); + recursive_pop(); + if (state) JUMP_TAG(state); + return result; + } +} +/********************************************************************** + + file.c - + + $Author: murphy $ + $Date: 2005-11-05 04:33:55 +0100 (Sa, 05 Nov 2005) $ + 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 + +#include "ruby.h" +#include "rubyio.h" +#include "rubysig.h" +#include "util.h" +#include "dln.h" + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#ifdef HAVE_SYS_FILE_H +# include <sys/file.h> +#else +int flock _((int, int)); +#endif + +#ifdef HAVE_SYS_PARAM_H +# include <sys/param.h> +#endif +#ifndef MAXPATHLEN +# define MAXPATHLEN 1024 +#endif + +#include <time.h> + +VALUE rb_time_new _((time_t, time_t)); + +#ifdef HAVE_UTIME_H +#include <utime.h> +#elif defined HAVE_SYS_UTIME_H +#include <sys/utime.h> +#endif + +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif + +#ifndef HAVE_STRING_H +char *strrchr _((const char*,const char)); +#endif + +#include <sys/types.h> +#include <sys/stat.h> + +#ifdef HAVE_SYS_MKDEV_H +#include <sys/mkdev.h> +#endif + +#if !defined HAVE_LSTAT && !defined lstat +#define lstat stat +#endif + +VALUE rb_cFile; +VALUE rb_mFileTest; +static VALUE rb_cStat; + +VALUE +rb_get_path(obj) + VALUE obj; +{ + VALUE tmp; + static ID to_path; + + rb_check_safe_obj(obj); + tmp = rb_check_string_type(obj); + if (!NIL_P(tmp)) goto exit; + + if (!to_path) { + to_path = rb_intern("to_path"); + } + if (rb_respond_to(obj, to_path)) { + obj = rb_funcall(obj, to_path, 0, 0); + } + tmp = rb_str_to_str(obj); + exit: + if (obj != tmp) { + rb_check_safe_obj(tmp); + } + return tmp; +} + +static long +apply2files(func, vargs, arg) + void (*func)(); + VALUE vargs; + void *arg; +{ + long i; + VALUE path; + struct RArray *args = RARRAY(vargs); + + rb_secure(4); + for (i=0; i<args->len; i++) { + path = rb_get_path(args->ptr[i]); + (*func)(StringValueCStr(path), arg); + } + + return args->len; +} + +/* + * call-seq: + * file.path -> filename + * + * Returns the pathname used to create <i>file</i> 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 <code>File::Stat</code> 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; +} + +/* + * call-seq: + * stat.dev => fixnum + * + * Returns an integer representing the device on which <i>stat</i> + * 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 <code>File_Stat#dev</code> or + * <code>nil</code>. + * + * 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 <code>File_Stat#dev</code> or + * <code>nil</code>. + * + * 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 <i>stat</i>. + * + * 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 + * <i>stat</i>. The meaning of the bits is platform dependent; on + * Unix systems, see <code>stat(2)</code>. + * + * 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 <i>stat</i>. + * + * 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 <i>stat</i>. + * + * 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 <i>stat</i>. + * + * 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 + * <i>stat</i> resides. Returns <code>nil</code> 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 <code>File_Stat#rdev</code> or + * <code>nil</code>. + * + * 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 <code>File_Stat#rdev</code> or + * <code>nil</code>. + * + * 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 <i>stat</i> 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 <code>nil</code> + * 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 <code>nil</code> 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 + * <code>Time</code>. + * + * 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 <i>stat</i>. + * + * 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 <i>stat</i> (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 <i>stat</i>. + * + * File.stat("/etc/passwd").inspect + * #=> "#<File::Stat dev=0xe000005, ino=1078078, mode=0100644, + * nlink=1, uid=0, gid=0, rdev=0x0, size=1374, blksize=4096, + * blocks=8, atime=Wed Dec 10 10:16:12 CST 2003, + * mtime=Fri Sep 12 15:41:41 CDT 2003, + * ctime=Mon Oct 27 11:20:27 CST 2003>" + */ + +static VALUE +rb_stat_inspect(self) + VALUE self; +{ + VALUE str; + int i; + static struct { + char *name; + VALUE (*func)(); + } 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; + + rb_secure(2); + tmp = rb_check_convert_type(file, T_FILE, "IO", "to_io"); + if (!NIL_P(tmp)) { + OpenFile *fptr; + + GetOpenFile(tmp, fptr); + return fstat(fptr->fd, st); + } + FilePathValue(file); + return stat(StringValueCStr(file), st); +} + +/* + * call-seq: + * File.stat(file_name) => stat + * + * Returns a <code>File::Stat</code> object for the named file (see + * <code>File::Stat</code>). + * + * 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; + + rb_secure(4); + FilePathValue(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 <em>ios</em> as an object of type + * <code>File::Stat</code>. + * + * 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(fptr->fd, &st) == -1) { + rb_sys_fail(fptr->path); + } + return stat_new(&st); +} + +/* + * call-seq: + * File.lstat(file_name) => stat + * + * Same as <code>File::stat</code>, 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; + + rb_secure(2); + FilePathValue(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 <code>IO#stat</code>, 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 +} + +static int +group_member(gid) + GETGROUPS_T gid; +{ +#ifndef _WIN32 + if (getgid() == 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; +} + +#ifndef S_IXUGO +# define S_IXUGO (S_IXUSR | S_IXGRP | S_IXOTH) +#endif + +int +eaccess(path, mode) + const char *path; + int mode; +{ +#if defined(S_IXGRP) && !defined(_WIN32) && !defined(__CYGWIN__) + 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 (getegid() == st.st_gid || 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 +} + + +/* + * Document-class: FileTest + * + * <code>FileTest</code> implements file test operations similar to + * those used in <code>File::Stat</code>. It exists as a standalone + * module, and its methods are also insinuated into the <code>File</code> + * class. (Note that this is not done by inclusion: the interpreter cheats). + * + */ + + +/* + * call-seq: + * File.directory?(file_name) => true or false + * + * Returns <code>true</code> if the named file is a directory, + * <code>false</code> 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 <code>true</code> 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 <code>true</code> 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) +# 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; + + rb_secure(2); + FilePathValue(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 <code>true</code> 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) +# 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 <code>true</code> 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 <code>true</code> 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 <code>true</code> 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 <code>true</code> if the named file is readable by the effective + * user id of this process. + */ + +static VALUE +test_r(obj, fname) + VALUE obj, fname; +{ + rb_secure(2); + FilePathValue(fname); + if (eaccess(StringValueCStr(fname), R_OK) < 0) return Qfalse; + return Qtrue; +} + +/* + * call-seq: + * File.readable_real?(file_name) => true or false + * + * Returns <code>true</code> if the named file is readable by the real + * user id of this process. + */ + +static VALUE +test_R(obj, fname) + VALUE obj, fname; +{ + rb_secure(2); + FilePathValue(fname); + if (access(StringValueCStr(fname), R_OK) < 0) return Qfalse; + return Qtrue; +} + +#ifndef S_IRUGO +# define S_IRUGO (S_IRUSR | S_IRGRP | S_IROTH) +#endif + +#ifndef S_IWUGO +# define S_IWUGO (S_IWUSR | S_IWGRP | S_IWOTH) +#endif + +/* + * call-seq: + * File.world_readable?(file_name) => fixnum or nil + * + * If <i>file_name</i> is readable by others, returns an integer + * representing the file permission bits of <i>file_name</i>. Returns + * <code>nil</code> otherwise. The meaning of the bits is platform + * dependent; on Unix systems, see <code>stat(2)</code>. + * + * File.world_readable?("/etc/passwd") # => 420 + * m = File.world_readable?("/etc/passwd") + * sprintf("%o", m) # => "644" + */ + +static VALUE +test_wr(obj, fname) + VALUE obj, fname; +{ +#ifdef S_IROTH + struct stat st; + + if (rb_stat(fname, &st) < 0) return Qnil; + if ((st.st_mode & (S_IROTH)) == S_IROTH) { + return UINT2NUM(st.st_mode & (S_IRUGO|S_IWUGO|S_IXUGO)); + } +#endif + return Qnil; +} + +/* + * call-seq: + * File.writable?(file_name) => true or false + * + * Returns <code>true</code> if the named file is writable by the effective + * user id of this process. + */ + +static VALUE +test_w(obj, fname) + VALUE obj, fname; +{ + rb_secure(2); + FilePathValue(fname); + if (eaccess(StringValueCStr(fname), W_OK) < 0) return Qfalse; + return Qtrue; +} + +/* + * call-seq: + * File.writable_real?(file_name) => true or false + * + * Returns <code>true</code> if the named file is writable by the real + * user id of this process. + */ + +static VALUE +test_W(obj, fname) + VALUE obj, fname; +{ + rb_secure(2); + FilePathValue(fname); + if (access(StringValueCStr(fname), W_OK) < 0) return Qfalse; + return Qtrue; +} + +/* + * call-seq: + * File.world_writable?(file_name) => fixnum or nil + * + * If <i>file_name</i> is writable by others, returns an integer + * representing the file permission bits of <i>file_name</i>. Returns + * <code>nil</code> otherwise. The meaning of the bits is platform + * dependent; on Unix systems, see <code>stat(2)</code>. + * + * File.world_writable?("/tmp") #=> 511 + * m = File.world_writable?("/tmp") + * sprintf("%o", m) #=> "777" + */ + +static VALUE +test_ww(obj, fname) + VALUE obj, fname; +{ +#ifdef S_IWOTH + struct stat st; + + if (rb_stat(fname, &st) < 0) return Qfalse; + if ((st.st_mode & (S_IWOTH)) == S_IWOTH) { + return UINT2NUM(st.st_mode & (S_IRUGO|S_IWUGO|S_IXUGO)); + } +#endif + return Qnil; +} + +/* + * call-seq: + * File.executable?(file_name) => true or false + * + * Returns <code>true</code> if the named file is executable by the effective + * user id of this process. + */ + +static VALUE +test_x(obj, fname) + VALUE obj, fname; +{ + rb_secure(2); + FilePathValue(fname); + if (eaccess(StringValueCStr(fname), X_OK) < 0) return Qfalse; + return Qtrue; +} + +/* + * call-seq: + * File.executable_real?(file_name) => true or false + * + * Returns <code>true</code> if the named file is executable by the real + * user id of this process. + */ + +static VALUE +test_X(obj, fname) + VALUE obj, fname; +{ + rb_secure(2); + FilePathValue(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 <code>true</code> 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 <code>true</code> 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.file?(file_name) => integer or nil + * + * Returns <code>nil</code> if <code>file_name</code> 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 <code>true</code> 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 <code>true</code> if the named file exists and the + * effective group id of the calling process is the owner of + * the file. Returns <code>false</code> 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 (st.st_gid == getegid()) 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; + + rb_secure(2); + FilePathValue(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 <code>true</code> if the named file is a 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 <code>true</code> if the named file is a 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 <code>true</code> if the named file is a 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.size(file_name) => integer + * + * Returns the size of <code>file_name</code>. + */ + +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 + * ``<code>file</code>'', ``<code>directory</code>'', + * ``<code>characterSpecial</code>'', ``<code>blockSpecial</code>'', + * ``<code>fifo</code>'', ``<code>link</code>'', + * ``<code>socket</code>'', or ``<code>unknown</code>''. + * + * 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; + + rb_secure(2); + FilePathValue(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 <code>Time</code> object) + * for <i>file</i>, or epoch if <i>file</i> 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(fptr->fd, &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 <i>file</i>. + * + * 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(fptr->fd, &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 <i>file</i> (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(fptr->fd, &st) == -1) { + rb_sys_fail(fptr->path); + } + return rb_time_new(st.st_ctime, 0); +} + +static void +chmod_internal(path, mode) + const char *path; + int mode; +{ + if (chmod(path, 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 <i>mode_int</i>. Actual effects are operating system + * dependent (see the beginning of this section). On Unix systems, see + * <code>chmod(2)</code> 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, (void *)(long)mode); + return LONG2FIX(n); +} + +/* + * call-seq: + * file.chmod(mode_int) => 0 + * + * Changes permission bits on <i>file</i> to the bit pattern + * represented by <i>mode_int</i>. Actual effects are platform + * dependent; on Unix systems, see <code>chmod(2)</code> for details. + * Follows symbolic links. Also see <code>File#lchmod</code>. + * + * 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(fptr->fd, 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(path, mode) + const char *path; + int mode; +{ + if (lchmod(path, mode) < 0) + rb_sys_fail(path); +} + +/* + * call-seq: + * File.lchmod(mode_int, file_name, ...) => integer + * + * Equivalent to <code>File::chmod</code>, 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(path, args) + const char *path; + struct chown_args *args; +{ + 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 <code>nil</code> 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 <i>file</i> 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 + * <code>nil</code> or -1 owner or group id is ignored. Follows + * symbolic links. See also <code>File#lchown</code>. + * + * 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 = NUM2INT(owner); + g = 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(fptr->fd, o, g) == -1) + rb_sys_fail(fptr->path); +#endif + + return INT2FIX(0); +} + +#if defined(HAVE_LCHOWN) && !defined(__CHECKER__) +static void +lchown_internal(path, args) + const char *path; + struct chown_args *args; +{ + 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 <code>File::chown</code>, 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(); + +#if defined(HAVE_UTIMES) && !defined(__CHECKER__) + +static void +utime_internal(path, tvp) + char *path; + struct timeval tvp[]; +{ + 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 tvp[2]; + long n; + + rb_scan_args(argc, argv, "2*", &atime, &mtime, &rest); + + 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, utp) + const char *path; + struct utimbuf *utp; +{ + 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; + + rb_scan_args(argc, argv, "2*", &atime, &mtime, &rest); + + tv = rb_time_timeval(atime); + utbuf.actime = tv.tv_sec; + tv = rb_time_timeval(mtime); + utbuf.modtime = tv.tv_sec; + + n = apply2files(utime_internal, rest, &utbuf); + 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 <i>new_name</i> if it already exists (raising a subclass + * of <code>SystemCallError</code>). 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 + rb_secure(2); + FilePathValue(from); + FilePathValue(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 <i>new_name</i> for the existing file + * <i>old_name</i>. Raises a <code>NotImplemented</code> 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 + rb_secure(2); + FilePathValue(from); + FilePathValue(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; + + rb_secure(2); + FilePathValue(path); + buf = xmalloc(size); + while ((rv = readlink(StringValueCStr(path), buf, size)) == size) { + 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(path) + const char *path; +{ + 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 <code>Dir::rmdir</code>. + */ + +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 + * <code>SystemCallError</code> 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; + + rb_secure(2); + FilePathValue(from); + FilePathValue(to); + src = StringValueCStr(from); + dst = StringValueCStr(to); + if (rename(src, dst) < 0) { +#if defined __CYGWIN__ + extern unsigned long __attribute__((stdcall)) GetLastError(); + errno = GetLastError(); /* This is a Cygwin bug */ +#elif defined DOSISH && !defined _WIN32 + if (errno == EEXIST +#if defined (__EMX__) + || errno == 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 <em>subtracted</em> from the + * default permissions, so a umask of <code>0222</code> 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); +} + +#if defined DOSISH +#define DOSISH_UNC +#define isdirsep(x) ((x) == '/' || (x) == '\\') +#else +#define isdirsep(x) ((x) == '/') +#endif +#ifndef CharNext /* defined as CharNext[AW] on Windows. */ +# if defined(DJGPP) +# define CharNext(p) ((p) + mblen(p, RUBY_MBCHAR_MAXSIZE)) +# else +# define CharNext(p) ((p) + 1) +# endif +#endif + +#ifdef __CYGWIN__ +#undef DOSISH +#define DOSISH_UNC +#define DOSISH_DRIVE_LETTER +#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; +} + +#define skipprefix rb_path_skip_prefix +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])) { + if (*(path = nextdirsep(path + 2))) + 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; +} + +#define chompdirsep rb_path_end +char * +rb_path_end(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; +} + +#define BUFCHECK(cond) do {\ + long bdiff = p - buf;\ + while (cond) {\ + buflen *= 2;\ + }\ + 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; +{ + char *s, *buf, *b, *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 */ + *p = '\0'; + if (!(b = strrdirsep(root))) { + *p = '/'; + } + else { + p = b; + } + b = ++s; + } + break; + case '/': +#if defined DOSISH || defined __CYGWIN__ + case '\\': +#endif + b = ++s; + break; + default: + /* ordinary path element, beginning don't move */ + break; + } + } + 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) { + BUFCHECK(bdiff + (s-b) >= buflen); + memcpy(++p, b, s-b); + p += s-b; + } + if (p == skiproot(buf) - 1) p++; + + if (tainted) OBJ_TAINT(result); + RSTRING(result)->len = p - buf; + *p = '\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 + * <i>dir_string</i> is given, in which case it will be used as the + * starting point. The given pathname may start with a + * ``<code>~</code>'', which expands to the process owner's home + * directory (the environment variable <code>HOME</code> must be set + * correctly). ``<code>~</code><i>user</i>'' 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, e) + const char *p, *e; +{ + int l1, l2; + + if (!e) return 0; + + l1 = chompdirsep(p) - p; + l2 = strlen(e); + if (l2 == 2 && e[1] == '*') { + e = strrchr(p, *e); + if (!e) return 0; + return e - p; + } + if (l1 < l2) return l1; + + if (strncmp(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 <i>file_name</i>, + * which must be formed using forward slashes (``<code>/</code>'') + * regardless of the separator used on the local file system. If + * <i>suffix</i> is given and present at the end of <i>file_name</i>, + * 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; + int f; + + 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; + if (!*(name = skiproot(name))) { + p = name - 1; + f = 1; +#ifdef DOSISH_DRIVE_LETTER + if (*p == ':') { + p++; + f = 0; + } +#endif + } + else if (!(p = strrdirsep(name))) { + if (NIL_P(fext) || !(f = rmext(name, StringValueCStr(fext)))) { + f = chompdirsep(name) - name; + if (f == RSTRING(fname)->len) return fname; + } + p = name; + } + else { + while (isdirsep(*p)) p++; /* skip last / */ + if (NIL_P(fext) || !(f = rmext(p, StringValueCStr(fext)))) { + f = chompdirsep(p) - p; + } + } + 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 <i>file_name</i> + * except the last one. The filename must be formed using forward + * slashes (``<code>/</code>'') 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; +{ + char *name, *root, *p; + VALUE dirname; + + name = StringValueCStr(fname); + root = skiproot(name); +#ifdef DOSISH_UNC + if (root > name + 2 && isdirsep(*name)) + 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("."); + dirname = rb_str_new(name, p - name); +#ifdef DOSISH_DRIVE_LETTER + if (root == name + 2 && name[1] == ':') + 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 <i>path</i> + * 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; +{ + char *name, *p, *e; + VALUE extname; + + name = StringValueCStr(fname); + p = strrdirsep(name); /* get the last path component */ + if (!p) + p = name; + else + p++; + + e = strrchr(p, '.'); /* get the last dot of the last component */ + if (!e || e == p) /* no dot, or the only dot is first? */ + return rb_str_new2(""); + extname = rb_str_new(e, chompdirsep(e) - e); /* keep the dot, too! */ + OBJ_INFECT(extname, fname); + return extname; +} + +/* + * call-seq: + * File.path(path) -> string + * + * Returns the string representation of the path + * + * File.path("/dev/null") #=> "/dev/null" + * File.path(Pathname.new("/tmp")) #=> "/tmp" + * + */ + +static VALUE +rb_file_s_path(klass, fname) + VALUE klass, fname; +{ + return rb_get_path(fname); +} + +/* + * 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 + * <code>File::dirname</code> and <code>File::basename</code>. + * + * 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, recur) + VALUE ary; + VALUE *arg; +{ + if (recur) return rb_str_new2("[...]"); + 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; + + 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; i<RARRAY(ary)->len; 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; i<RARRAY(ary)->len; i++) { + tmp = RARRAY(ary)->ptr[i]; + switch (TYPE(tmp)) { + case T_STRING: + break; + case T_ARRAY: + { + VALUE args[2]; + + args[0] = tmp; + args[1] = sep; + tmp = rb_exec_recursive(file_inspect_join, ary, (VALUE)args); + } + break; + default: + tmp = rb_obj_as_string(tmp); + } + name = StringValueCStr(result); + if (i > 0 && !NIL_P(sep) && !*chompdirsep(name)) + 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 + * <code>File::SEPARATOR</code>. + * + * 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 <i>file_name</i> to be at most <i>integer</i> + * 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); + FilePathValue(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 <i>file</i> to at most <i>integer</i> 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; + 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"); + } + rb_io_flush(obj); +#ifdef HAVE_TRUNCATE + if (ftruncate(fptr->fd, pos) < 0) + rb_sys_fail(fptr->path); +#else +# ifdef HAVE_CHSIZE + if (chsize(fptr->fd, 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 + +#if 1 +static int +rb_thread_flock(fd, op, fptr) + int fd, op; + OpenFile *fptr; +{ + if (rb_thread_alone() || (op & LOCK_NB)) { + return flock(fd, op); + } + 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; +} +#define flock(fd, op) rb_thread_flock(fd, op, fptr) +#endif + +/* + * call-seq: + * file.flock (locking_constant ) => 0 or false + * + * Locks or unlocks a file according to <i>locking_constant</i> (a + * logical <em>or</em> of the values in the table below). + * Returns <code>false</code> if <code>File::LOCK_NB</code> 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) { + rb_io_flush(obj); + } + retry: + if (flock(fptr->fd, 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; + + rb_secure(2); + n+=1; + if (n != argc) rb_raise(rb_eArgError, "wrong number of arguments (%d for %d)", argc, n); + for (i=1; i<n; i++) { + switch (TYPE(argv[i])) { + case T_STRING: + default: + FilePathValue(argv[i]); + break; + case T_FILE: + break; + } + } +} + +#define CHECK(n) test_check((n), argc, argv) + +/* + * call-seq: + * test(int_cmd, file1 [, file2] ) => obj + * + * Uses the integer <i>aCmd</i> to perform various tests on + * <i>file1</i> (first table below) or on <i>file1</i> and + * <i>file2</i> (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 files 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 files exists and is a symbolic link + * ?M | Time | Last modification time for file1 + * ?o | boolean | True if files 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 files 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 is a hard link to file2 + * ?= | 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 (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_dev == st2.st_dev && st1.st_ino == st2.st_ino) + 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; + + 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 <code>File::Stat</code> encapsulate common status + * information for <code>File</code> objects. The information is + * recorded at the moment the <code>File::Stat</code> object is + * created; changes made to the file after that point will not be + * reflected. <code>File::Stat</code> objects are returned by + * <code>IO#stat</code>, <code>File::stat</code>, + * <code>File#lstat</code>, and <code>File::lstat</code>. Many of these + * methods return platform-specific values, and not all values are + * meaningful on all systems. See also <code>Kernel#test</code>. + */ + +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; + + rb_secure(2); + FilePathValue(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 <i>stat</i>. The return string is one of: + * ``<code>file</code>'', ``<code>directory</code>'', + * ``<code>characterSpecial</code>'', ``<code>blockSpecial</code>'', + * ``<code>fifo</code>'', ``<code>link</code>'', + * ``<code>socket</code>'', or ``<code>unknown</code>''. + * + * 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 <code>true</code> if <i>stat</i> is a directory, + * <code>false</code> 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 <code>true</code> if the operating system supports pipes and + * <i>stat</i> is a pipe; <code>false</code> 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 <code>true</code> if <i>stat</i> is a symbolic link, + * <code>false</code> if it isn't or if the operating system doesn't + * support this feature. As <code>File::stat</code> automatically + * follows symbolic links, <code>symlink?</code> will always be + * <code>false</code> for an object returned by + * <code>File::stat</code>. + * + * 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 <code>true</code> if <i>stat</i> is a socket, + * <code>false</code> 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; +} + |