Cannot retrieve contributors at this time
/********************************************************************** | |
eval.c - | |
$Author$ | |
created at: Thu Jun 10 14:22:17 JST 1993 | |
Copyright (C) 1993-2007 Yukihiro Matsumoto | |
Copyright (C) 2000 Network Applied Communication Laboratory, Inc. | |
Copyright (C) 2000 Information-technology Promotion Agency, Japan | |
**********************************************************************/ | |
#include "internal.h" | |
#include "eval_intern.h" | |
#include "iseq.h" | |
#include "gc.h" | |
#include "ruby/vm.h" | |
#include "vm_core.h" | |
#include "probes_helper.h" | |
NORETURN(void rb_raise_jump(VALUE, VALUE)); | |
VALUE rb_eLocalJumpError; | |
VALUE rb_eSysStackError; | |
#define exception_error GET_VM()->special_exceptions[ruby_error_reenter] | |
#include "eval_error.c" | |
#include "eval_jump.c" | |
#define CLASS_OR_MODULE_P(obj) \ | |
(!SPECIAL_CONST_P(obj) && \ | |
(BUILTIN_TYPE(obj) == T_CLASS || BUILTIN_TYPE(obj) == T_MODULE)) | |
/* Initializes the Ruby VM and builtin libraries. | |
* @retval 0 if succeeded. | |
* @retval non-zero an error occurred. | |
*/ | |
int | |
ruby_setup(void) | |
{ | |
static int initialized = 0; | |
int state; | |
if (initialized) | |
return 0; | |
initialized = 1; | |
ruby_init_stack((void *)&state); | |
Init_BareVM(); | |
Init_heap(); | |
Init_vm_objects(); | |
Init_frozen_strings(); | |
PUSH_TAG(); | |
if ((state = EXEC_TAG()) == 0) { | |
rb_call_inits(); | |
ruby_prog_init(); | |
GET_VM()->running = 1; | |
} | |
POP_TAG(); | |
return state; | |
} | |
/* Calls ruby_setup() and check error. | |
* | |
* Prints errors and calls exit(3) if an error occurred. | |
*/ | |
void | |
ruby_init(void) | |
{ | |
int state = ruby_setup(); | |
if (state) { | |
if (RTEST(ruby_debug)) | |
error_print(); | |
exit(EXIT_FAILURE); | |
} | |
} | |
/*! Processes command line arguments and compiles the Ruby source to execute. | |
* | |
* This function does: | |
* \li Processes the given command line flags and arguments for ruby(1) | |
* \li compiles the source code from the given argument, -e or stdin, and | |
* \li returns the compiled source as an opaque pointer to an internal data structure | |
* | |
* @return an opaque pointer to the compiled source or an internal special value. | |
* @sa ruby_executable_node(). | |
*/ | |
void * | |
ruby_options(int argc, char **argv) | |
{ | |
int state; | |
void *volatile iseq = 0; | |
ruby_init_stack((void *)&iseq); | |
PUSH_TAG(); | |
if ((state = EXEC_TAG()) == 0) { | |
SAVE_ROOT_JMPBUF(GET_THREAD(), iseq = ruby_process_options(argc, argv)); | |
} | |
else { | |
rb_clear_trace_func(); | |
state = error_handle(state); | |
iseq = (void *)INT2FIX(state); | |
} | |
POP_TAG(); | |
return iseq; | |
} | |
static void | |
ruby_finalize_0(void) | |
{ | |
PUSH_TAG(); | |
if (EXEC_TAG() == 0) { | |
rb_trap_exit(); | |
} | |
POP_TAG(); | |
rb_exec_end_proc(); | |
rb_clear_trace_func(); | |
} | |
static void | |
ruby_finalize_1(void) | |
{ | |
ruby_sig_finalize(); | |
GET_THREAD()->errinfo = Qnil; | |
rb_gc_call_finalizer_at_exit(); | |
} | |
/** Runs the VM finalization processes. | |
* | |
* <code>END{}</code> and procs registered by <code>Kernel.#at_exit</code> are | |
* executed here. See the Ruby language spec for more details. | |
* | |
* @note This function is allowed to raise an exception if an error occurred. | |
*/ | |
void | |
ruby_finalize(void) | |
{ | |
ruby_finalize_0(); | |
ruby_finalize_1(); | |
} | |
/** Destructs the VM. | |
* | |
* Runs the VM finalization processes as well as ruby_finalize(), and frees | |
* resources used by the VM. | |
* | |
* @param ex Default value to the return value. | |
* @return If an error occurred returns a non-zero. If otherwise, returns the | |
* given ex. | |
* @note This function does not raise any exception. | |
*/ | |
int | |
ruby_cleanup(volatile int ex) | |
{ | |
int state; | |
volatile VALUE errs[2]; | |
rb_thread_t *th = GET_THREAD(); | |
int nerr; | |
rb_threadptr_interrupt(th); | |
rb_threadptr_check_signal(th); | |
PUSH_TAG(); | |
if ((state = EXEC_TAG()) == 0) { | |
SAVE_ROOT_JMPBUF(th, { RUBY_VM_CHECK_INTS(th); }); | |
} | |
POP_TAG(); | |
errs[1] = th->errinfo; | |
th->safe_level = 0; | |
ruby_init_stack(&errs[STACK_UPPER(errs, 0, 1)]); | |
PUSH_TAG(); | |
if ((state = EXEC_TAG()) == 0) { | |
SAVE_ROOT_JMPBUF(th, ruby_finalize_0()); | |
} | |
POP_TAG(); | |
/* protect from Thread#raise */ | |
th->status = THREAD_KILLED; | |
errs[0] = th->errinfo; | |
PUSH_TAG(); | |
if ((state = EXEC_TAG()) == 0) { | |
SAVE_ROOT_JMPBUF(th, rb_thread_terminate_all()); | |
} | |
else if (ex == 0) { | |
ex = state; | |
} | |
th->errinfo = errs[1]; | |
ex = error_handle(ex); | |
#if EXIT_SUCCESS != 0 || EXIT_FAILURE != 1 | |
switch (ex) { | |
#if EXIT_SUCCESS != 0 | |
case 0: ex = EXIT_SUCCESS; break; | |
#endif | |
#if EXIT_FAILURE != 1 | |
case 1: ex = EXIT_FAILURE; break; | |
#endif | |
} | |
#endif | |
state = 0; | |
for (nerr = 0; nerr < numberof(errs); ++nerr) { | |
VALUE err = errs[nerr]; | |
if (!RTEST(err)) continue; | |
/* th->errinfo contains a NODE while break'ing */ | |
if (RB_TYPE_P(err, T_NODE)) continue; | |
if (rb_obj_is_kind_of(err, rb_eSystemExit)) { | |
ex = sysexit_status(err); | |
break; | |
} | |
else if (rb_obj_is_kind_of(err, rb_eSignal)) { | |
VALUE sig = rb_iv_get(err, "signo"); | |
state = NUM2INT(sig); | |
break; | |
} | |
else if (ex == EXIT_SUCCESS) { | |
ex = EXIT_FAILURE; | |
} | |
} | |
ruby_finalize_1(); | |
/* unlock again if finalizer took mutexes. */ | |
rb_threadptr_unlock_all_locking_mutexes(GET_THREAD()); | |
POP_TAG(); | |
rb_thread_stop_timer_thread(1); | |
ruby_vm_destruct(GET_VM()); | |
if (state) ruby_default_signal(state); | |
return ex; | |
} | |
static int | |
ruby_exec_internal(void *n) | |
{ | |
volatile int state; | |
VALUE iseq = (VALUE)n; | |
rb_thread_t *th = GET_THREAD(); | |
if (!n) return 0; | |
PUSH_TAG(); | |
if ((state = EXEC_TAG()) == 0) { | |
SAVE_ROOT_JMPBUF(th, { | |
th->base_block = 0; | |
rb_iseq_eval_main(iseq); | |
}); | |
} | |
POP_TAG(); | |
return state; | |
} | |
/*! Calls ruby_cleanup() and exits the process */ | |
void | |
ruby_stop(int ex) | |
{ | |
exit(ruby_cleanup(ex)); | |
} | |
/*! Checks the return value of ruby_options(). | |
* @param n return value of ruby_options(). | |
* @param status pointer to the exit status of this process. | |
* | |
* ruby_options() sometimes returns a special value to indicate this process | |
* should immediately exit. This function checks if the case. Also stores the | |
* exit status that the caller have to pass to exit(3) into | |
* <code>*status</code>. | |
* | |
* @retval non-zero if the given opaque pointer is actually a compiled source. | |
* @retval 0 if the given value is such a special value. | |
*/ | |
int | |
ruby_executable_node(void *n, int *status) | |
{ | |
VALUE v = (VALUE)n; | |
int s; | |
switch (v) { | |
case Qtrue: s = EXIT_SUCCESS; break; | |
case Qfalse: s = EXIT_FAILURE; break; | |
default: | |
if (!FIXNUM_P(v)) return TRUE; | |
s = FIX2INT(v); | |
} | |
if (status) *status = s; | |
return FALSE; | |
} | |
/*! Runs the given compiled source and exits this process. | |
* @retval 0 if successfully run the source | |
* @retval non-zero if an error occurred. | |
*/ | |
int | |
ruby_run_node(void *n) | |
{ | |
int status; | |
if (!ruby_executable_node(n, &status)) { | |
ruby_cleanup(0); | |
return status; | |
} | |
return ruby_cleanup(ruby_exec_node(n)); | |
} | |
/*! Runs the given compiled source */ | |
int | |
ruby_exec_node(void *n) | |
{ | |
ruby_init_stack((void *)&n); | |
return ruby_exec_internal(n); | |
} | |
/* | |
* 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(void) | |
{ | |
VALUE ary = rb_ary_new(); | |
const NODE *cref = rb_vm_cref(); | |
while (cref && cref->nd_next) { | |
VALUE klass = cref->nd_clss; | |
if (!(cref->flags & NODE_FL_CREF_PUSHED_BY_EVAL) && | |
!NIL_P(klass)) { | |
rb_ary_push(ary, klass); | |
} | |
cref = cref->nd_next; | |
} | |
return ary; | |
} | |
/* | |
* call-seq: | |
* Module.constants -> array | |
* Module.constants(inherited) -> array | |
* | |
* In the first form, returns an array of the names of all | |
* constants accessible from the point of call. | |
* This list includes the names of all modules and classes | |
* defined in the global scope. | |
* | |
* Module.constants.first(4) | |
* # => [:ARGF, :ARGV, :ArgumentError, :Array] | |
* | |
* Module.constants.include?(:SEEK_SET) # => false | |
* | |
* class IO | |
* Module.constants.include?(:SEEK_SET) # => true | |
* end | |
* | |
* The second form calls the instance method +constants+. | |
*/ | |
static VALUE | |
rb_mod_s_constants(int argc, VALUE *argv, VALUE mod) | |
{ | |
const NODE *cref = rb_vm_cref(); | |
VALUE klass; | |
VALUE cbase = 0; | |
void *data = 0; | |
if (argc > 0 || mod != rb_cModule) { | |
return rb_mod_constants(argc, argv, mod); | |
} | |
while (cref) { | |
klass = cref->nd_clss; | |
if (!(cref->flags & NODE_FL_CREF_PUSHED_BY_EVAL) && | |
!NIL_P(klass)) { | |
data = rb_mod_const_at(cref->nd_clss, data); | |
if (!cbase) { | |
cbase = klass; | |
} | |
} | |
cref = cref->nd_next; | |
} | |
if (cbase) { | |
data = rb_mod_const_of(cbase, data); | |
} | |
return rb_const_list(data); | |
} | |
void | |
rb_frozen_class_p(VALUE klass) | |
{ | |
if (SPECIAL_CONST_P(klass)) { | |
noclass: | |
Check_Type(klass, T_CLASS); | |
} | |
if (OBJ_FROZEN(klass)) { | |
const char *desc; | |
if (FL_TEST(klass, FL_SINGLETON)) { | |
desc = "object"; | |
klass = rb_ivar_get(klass, id__attached__); | |
if (!SPECIAL_CONST_P(klass)) { | |
switch (BUILTIN_TYPE(klass)) { | |
case T_MODULE: | |
case T_ICLASS: | |
desc = "Module"; | |
break; | |
case T_CLASS: | |
desc = "Class"; | |
break; | |
} | |
} | |
} | |
else { | |
switch (BUILTIN_TYPE(klass)) { | |
case T_MODULE: | |
case T_ICLASS: | |
desc = "module"; | |
break; | |
case T_CLASS: | |
desc = "class"; | |
break; | |
default: | |
goto noclass; | |
} | |
} | |
rb_error_frozen(desc); | |
} | |
} | |
NORETURN(static void rb_longjmp(int, volatile VALUE, VALUE)); | |
static VALUE get_errinfo(void); | |
static VALUE get_thread_errinfo(rb_thread_t *th); | |
static VALUE | |
exc_setup_cause(VALUE exc, VALUE cause) | |
{ | |
ID id_cause; | |
CONST_ID(id_cause, "cause"); | |
#if SUPPORT_JOKE | |
if (NIL_P(cause)) { | |
ID id_true_cause; | |
CONST_ID(id_true_cause, "true_cause"); | |
cause = rb_attr_get(rb_eFatal, id_true_cause); | |
if (NIL_P(cause)) { | |
cause = rb_exc_new_cstr(rb_eFatal, "because using such Ruby"); | |
rb_ivar_set(cause, id_cause, INT2FIX(42)); /* the answer */ | |
OBJ_FREEZE(cause); | |
rb_ivar_set(rb_eFatal, id_true_cause, cause); | |
} | |
} | |
#endif | |
if (!NIL_P(cause) && cause != exc) { | |
rb_ivar_set(exc, id_cause, cause); | |
} | |
return exc; | |
} | |
static inline int | |
sysstack_error_p(VALUE exc) | |
{ | |
return exc == sysstack_error || (!SPECIAL_CONST_P(exc) && RBASIC_CLASS(exc) == rb_eSysStackError); | |
} | |
static void | |
setup_exception(rb_thread_t *th, int tag, volatile VALUE mesg, VALUE cause) | |
{ | |
VALUE e; | |
const char *file = 0; | |
volatile int line = 0; | |
int nocause = 0; | |
if (NIL_P(mesg)) { | |
mesg = th->errinfo; | |
if (INTERNAL_EXCEPTION_P(mesg)) JUMP_TAG(TAG_FATAL); | |
nocause = 1; | |
} | |
if (NIL_P(mesg)) { | |
mesg = rb_exc_new(rb_eRuntimeError, 0, 0); | |
nocause = 0; | |
} | |
if (cause == Qundef) { | |
cause = nocause ? Qnil : get_thread_errinfo(th); | |
} | |
exc_setup_cause(mesg, cause); | |
file = rb_sourcefile(); | |
if (file) line = rb_sourceline(); | |
if (file && !NIL_P(mesg)) { | |
VALUE at; | |
if (sysstack_error_p(mesg)) { | |
if (NIL_P(rb_attr_get(mesg, idBt))) { | |
at = rb_vm_backtrace_object(); | |
if (mesg == sysstack_error) { | |
mesg = ruby_vm_sysstack_error_copy(); | |
} | |
rb_ivar_set(mesg, idBt, at); | |
rb_ivar_set(mesg, idBt_locations, at); | |
} | |
} | |
else if (NIL_P(get_backtrace(mesg))) { | |
at = rb_vm_backtrace_object(); | |
if (OBJ_FROZEN(mesg)) { | |
mesg = rb_obj_dup(mesg); | |
} | |
rb_ivar_set(mesg, idBt_locations, at); | |
set_backtrace(mesg, at); | |
} | |
} | |
if (!NIL_P(mesg)) { | |
th->errinfo = mesg; | |
} | |
if (RTEST(ruby_debug) && !NIL_P(e = th->errinfo) && | |
!rb_obj_is_kind_of(e, rb_eSystemExit)) { | |
int status; | |
mesg = e; | |
PUSH_TAG(); | |
if ((status = EXEC_TAG()) == 0) { | |
th->errinfo = Qnil; | |
e = rb_obj_as_string(mesg); | |
th->errinfo = mesg; | |
if (file && line) { | |
warn_printf("Exception `%"PRIsVALUE"' at %s:%d - %"PRIsVALUE"\n", | |
rb_obj_class(mesg), file, line, e); | |
} | |
else if (file) { | |
warn_printf("Exception `%"PRIsVALUE"' at %s - %"PRIsVALUE"\n", | |
rb_obj_class(mesg), file, e); | |
} | |
else { | |
warn_printf("Exception `%"PRIsVALUE"' - %"PRIsVALUE"\n", | |
rb_obj_class(mesg), e); | |
} | |
} | |
POP_TAG(); | |
if (status == TAG_FATAL && th->errinfo == exception_error) { | |
th->errinfo = mesg; | |
} | |
else if (status) { | |
rb_threadptr_reset_raised(th); | |
JUMP_TAG(status); | |
} | |
} | |
if (rb_threadptr_set_raised(th)) { | |
th->errinfo = exception_error; | |
rb_threadptr_reset_raised(th); | |
JUMP_TAG(TAG_FATAL); | |
} | |
if (tag != TAG_FATAL) { | |
if (RUBY_DTRACE_RAISE_ENABLED()) { | |
RUBY_DTRACE_RAISE(rb_obj_classname(th->errinfo), | |
rb_sourcefile(), | |
rb_sourceline()); | |
} | |
EXEC_EVENT_HOOK(th, RUBY_EVENT_RAISE, th->cfp->self, 0, 0, mesg); | |
} | |
} | |
static void | |
rb_longjmp(int tag, volatile VALUE mesg, VALUE cause) | |
{ | |
rb_thread_t *th = GET_THREAD(); | |
setup_exception(th, tag, mesg, cause); | |
rb_thread_raised_clear(th); | |
JUMP_TAG(tag); | |
} | |
static VALUE make_exception(int argc, const VALUE *argv, int isstr); | |
void | |
rb_exc_raise(VALUE mesg) | |
{ | |
if (!NIL_P(mesg)) { | |
mesg = make_exception(1, &mesg, FALSE); | |
} | |
rb_longjmp(TAG_RAISE, mesg, Qundef); | |
} | |
void | |
rb_exc_fatal(VALUE mesg) | |
{ | |
if (!NIL_P(mesg)) { | |
mesg = make_exception(1, &mesg, FALSE); | |
} | |
rb_longjmp(TAG_FATAL, mesg, Qnil); | |
} | |
void | |
rb_interrupt(void) | |
{ | |
rb_raise(rb_eInterrupt, "%s", ""); | |
} | |
enum {raise_opt_cause, raise_max_opt}; | |
static int | |
extract_raise_opts(int argc, VALUE *argv, VALUE *opts) | |
{ | |
int i; | |
if (argc > 0) { | |
VALUE opt = argv[argc-1]; | |
if (RB_TYPE_P(opt, T_HASH)) { | |
if (!RHASH_EMPTY_P(opt)) { | |
ID keywords[1]; | |
CONST_ID(keywords[0], "cause"); | |
rb_get_kwargs(opt, keywords, 0, -1-raise_max_opt, opts); | |
if (RHASH_EMPTY_P(opt)) --argc; | |
return argc; | |
} | |
} | |
} | |
for (i = 0; i < raise_max_opt; ++i) { | |
opts[i] = Qundef; | |
} | |
return argc; | |
} | |
/* | |
* 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(int argc, VALUE *argv) | |
{ | |
VALUE err; | |
VALUE opts[raise_max_opt], *const cause = &opts[raise_opt_cause]; | |
argc = extract_raise_opts(argc, argv, opts); | |
if (argc == 0) { | |
if (*cause != Qundef) { | |
rb_raise(rb_eArgError, "only cause is given with no arguments"); | |
} | |
err = get_errinfo(); | |
if (!NIL_P(err)) { | |
argc = 1; | |
argv = &err; | |
} | |
} | |
rb_raise_jump(rb_make_exception(argc, argv), *cause); | |
UNREACHABLE; | |
} | |
static VALUE | |
make_exception(int argc, const VALUE *argv, int isstr) | |
{ | |
VALUE mesg, exc; | |
int n; | |
mesg = Qnil; | |
switch (argc) { | |
case 0: | |
break; | |
case 1: | |
exc = argv[0]; | |
if (NIL_P(exc)) | |
break; | |
if (isstr) { | |
mesg = rb_check_string_type(exc); | |
if (!NIL_P(mesg)) { | |
mesg = rb_exc_new3(rb_eRuntimeError, mesg); | |
break; | |
} | |
} | |
n = 0; | |
goto exception_call; | |
case 2: | |
case 3: | |
exc = argv[0]; | |
n = 1; | |
exception_call: | |
if (sysstack_error_p(exc)) return exc; | |
mesg = rb_check_funcall(exc, idException, n, argv+1); | |
if (mesg == Qundef) { | |
rb_raise(rb_eTypeError, "exception class/object expected"); | |
} | |
break; | |
default: | |
rb_check_arity(argc, 0, 3); | |
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; | |
} | |
VALUE | |
rb_make_exception(int argc, const VALUE *argv) | |
{ | |
return make_exception(argc, argv, TRUE); | |
} | |
void | |
rb_raise_jump(VALUE mesg, VALUE cause) | |
{ | |
rb_thread_t *th = GET_THREAD(); | |
rb_control_frame_t *cfp = th->cfp; | |
VALUE klass = cfp->me->klass; | |
VALUE self = cfp->self; | |
ID mid = cfp->me->called_id; | |
th->cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(th->cfp); | |
EXEC_EVENT_HOOK(th, RUBY_EVENT_C_RETURN, self, mid, klass, Qnil); | |
setup_exception(th, TAG_RAISE, mesg, cause); | |
rb_thread_raised_clear(th); | |
JUMP_TAG(TAG_RAISE); | |
} | |
void | |
rb_jump_tag(int tag) | |
{ | |
JUMP_TAG(tag); | |
} | |
int | |
rb_block_given_p(void) | |
{ | |
rb_thread_t *th = GET_THREAD(); | |
if (rb_vm_control_frame_block_ptr(th->cfp)) { | |
return TRUE; | |
} | |
else { | |
return FALSE; | |
} | |
} | |
int | |
rb_iterator_p(void) | |
{ | |
return rb_block_given_p(); | |
} | |
VALUE rb_eThreadError; | |
void | |
rb_need_block(void) | |
{ | |
if (!rb_block_given_p()) { | |
rb_vm_localjump_error("no block given", Qnil, 0); | |
} | |
} | |
VALUE | |
rb_rescue2(VALUE (* b_proc) (ANYARGS), VALUE data1, | |
VALUE (* r_proc) (ANYARGS), VALUE data2, ...) | |
{ | |
int state; | |
rb_thread_t *th = GET_THREAD(); | |
rb_control_frame_t *cfp = th->cfp; | |
volatile VALUE result = Qfalse; | |
volatile VALUE e_info = th->errinfo; | |
va_list args; | |
TH_PUSH_TAG(th); | |
if ((state = TH_EXEC_TAG()) == 0) { | |
retry_entry: | |
result = (*b_proc) (data1); | |
} | |
else if (result) { | |
/* escape from r_proc */ | |
if (state == TAG_RETRY) { | |
state = 0; | |
th->errinfo = Qnil; | |
result = Qfalse; | |
goto retry_entry; | |
} | |
} | |
else { | |
rb_vm_rewind_cfp(th, cfp); | |
if (state == TAG_RAISE) { | |
int handle = FALSE; | |
VALUE eclass; | |
va_init_list(args, data2); | |
while ((eclass = va_arg(args, VALUE)) != 0) { | |
if (rb_obj_is_kind_of(th->errinfo, eclass)) { | |
handle = TRUE; | |
break; | |
} | |
} | |
va_end(args); | |
if (handle) { | |
result = Qnil; | |
state = 0; | |
if (r_proc) { | |
result = (*r_proc) (data2, th->errinfo); | |
} | |
th->errinfo = e_info; | |
} | |
} | |
} | |
TH_POP_TAG(); | |
if (state) | |
JUMP_TAG(state); | |
return result; | |
} | |
VALUE | |
rb_rescue(VALUE (* b_proc)(ANYARGS), VALUE data1, | |
VALUE (* r_proc)(ANYARGS), VALUE data2) | |
{ | |
return rb_rescue2(b_proc, data1, r_proc, data2, rb_eStandardError, | |
(VALUE)0); | |
} | |
VALUE | |
rb_protect(VALUE (* proc) (VALUE), VALUE data, int * state) | |
{ | |
volatile VALUE result = Qnil; | |
volatile int status; | |
rb_thread_t *th = GET_THREAD(); | |
rb_control_frame_t *cfp = th->cfp; | |
struct rb_vm_protect_tag protect_tag; | |
rb_jmpbuf_t org_jmpbuf; | |
protect_tag.prev = th->protect_tag; | |
TH_PUSH_TAG(th); | |
th->protect_tag = &protect_tag; | |
MEMCPY(&org_jmpbuf, &(th)->root_jmpbuf, rb_jmpbuf_t, 1); | |
if ((status = TH_EXEC_TAG()) == 0) { | |
SAVE_ROOT_JMPBUF(th, result = (*proc) (data)); | |
} | |
else { | |
rb_vm_rewind_cfp(th, cfp); | |
} | |
MEMCPY(&(th)->root_jmpbuf, &org_jmpbuf, rb_jmpbuf_t, 1); | |
th->protect_tag = protect_tag.prev; | |
TH_POP_TAG(); | |
if (state) { | |
*state = status; | |
} | |
return result; | |
} | |
VALUE | |
rb_ensure(VALUE (*b_proc)(ANYARGS), VALUE data1, VALUE (*e_proc)(ANYARGS), VALUE data2) | |
{ | |
int state; | |
volatile VALUE result = Qnil; | |
volatile VALUE errinfo; | |
rb_thread_t *const th = GET_THREAD(); | |
rb_ensure_list_t ensure_list; | |
ensure_list.entry.marker = 0; | |
ensure_list.entry.e_proc = e_proc; | |
ensure_list.entry.data2 = data2; | |
ensure_list.next = th->ensure_list; | |
th->ensure_list = &ensure_list; | |
PUSH_TAG(); | |
if ((state = EXEC_TAG()) == 0) { | |
result = (*b_proc) (data1); | |
} | |
POP_TAG(); | |
errinfo = th->errinfo; | |
th->ensure_list=ensure_list.next; | |
(*ensure_list.entry.e_proc)(ensure_list.entry.data2); | |
th->errinfo = errinfo; | |
if (state) | |
JUMP_TAG(state); | |
return result; | |
} | |
static const rb_method_entry_t * | |
method_entry_of_iseq(rb_control_frame_t *cfp, rb_iseq_t *iseq) | |
{ | |
rb_thread_t *th = GET_THREAD(); | |
rb_control_frame_t *cfp_limit; | |
cfp_limit = (rb_control_frame_t *)(th->stack + th->stack_size); | |
while (cfp_limit > cfp) { | |
if (cfp->iseq == iseq) | |
return cfp->me; | |
cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); | |
} | |
return 0; | |
} | |
static ID | |
frame_func_id(rb_control_frame_t *cfp) | |
{ | |
const rb_method_entry_t *me_local; | |
rb_iseq_t *iseq = cfp->iseq; | |
if (cfp->me) { | |
return cfp->me->def->original_id; | |
} | |
while (iseq) { | |
if (RUBY_VM_IFUNC_P(iseq)) { | |
NODE *ifunc = (NODE *)iseq; | |
if (ifunc->nd_aid) return ifunc->nd_aid; | |
return idIFUNC; | |
} | |
me_local = method_entry_of_iseq(cfp, iseq); | |
if (me_local) { | |
cfp->me = me_local; | |
return me_local->def->original_id; | |
} | |
if (iseq->defined_method_id) { | |
return iseq->defined_method_id; | |
} | |
if (iseq->local_iseq == iseq) { | |
break; | |
} | |
iseq = iseq->parent_iseq; | |
} | |
return 0; | |
} | |
static ID | |
frame_called_id(rb_control_frame_t *cfp) | |
{ | |
const rb_method_entry_t *me_local; | |
rb_iseq_t *iseq = cfp->iseq; | |
if (cfp->me) { | |
return cfp->me->called_id; | |
} | |
while (iseq) { | |
if (RUBY_VM_IFUNC_P(iseq)) { | |
NODE *ifunc = (NODE *)iseq; | |
if (ifunc->nd_aid) return ifunc->nd_aid; | |
return idIFUNC; | |
} | |
me_local = method_entry_of_iseq(cfp, iseq); | |
if (me_local) { | |
cfp->me = me_local; | |
return me_local->called_id; | |
} | |
if (iseq->defined_method_id) { | |
return iseq->defined_method_id; | |
} | |
if (iseq->local_iseq == iseq) { | |
break; | |
} | |
iseq = iseq->parent_iseq; | |
} | |
return 0; | |
} | |
ID | |
rb_frame_this_func(void) | |
{ | |
return frame_func_id(GET_THREAD()->cfp); | |
} | |
ID | |
rb_frame_callee(void) | |
{ | |
return frame_called_id(GET_THREAD()->cfp); | |
} | |
static rb_control_frame_t * | |
previous_frame(rb_thread_t *th) | |
{ | |
rb_control_frame_t *prev_cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(th->cfp); | |
/* check if prev_cfp can be accessible */ | |
if ((void *)(th->stack + th->stack_size) == (void *)(prev_cfp)) { | |
return 0; | |
} | |
return prev_cfp; | |
} | |
static ID | |
prev_frame_callee(void) | |
{ | |
rb_control_frame_t *prev_cfp = previous_frame(GET_THREAD()); | |
if (!prev_cfp) return 0; | |
return frame_called_id(prev_cfp); | |
} | |
static ID | |
prev_frame_func(void) | |
{ | |
rb_control_frame_t *prev_cfp = previous_frame(GET_THREAD()); | |
if (!prev_cfp) return 0; | |
return frame_func_id(prev_cfp); | |
} | |
ID | |
rb_frame_last_func(void) | |
{ | |
rb_thread_t *th = GET_THREAD(); | |
rb_control_frame_t *cfp = th->cfp; | |
ID mid; | |
while (!(mid = frame_func_id(cfp)) && | |
(cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp), | |
!RUBY_VM_CONTROL_FRAME_STACK_OVERFLOW_P(th, cfp))); | |
return mid; | |
} | |
/* | |
* 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(VALUE module, VALUE include) | |
{ | |
if (!CLASS_OR_MODULE_P(include)) { | |
Check_Type(include, T_CLASS); | |
} | |
rb_include_module(include, module); | |
return module; | |
} | |
/* | |
* call-seq: | |
* include(module, ...) -> self | |
* | |
* Invokes <code>Module.append_features</code> on each parameter in reverse order. | |
*/ | |
static VALUE | |
rb_mod_include(int argc, VALUE *argv, VALUE module) | |
{ | |
int i; | |
ID id_append_features, id_included; | |
CONST_ID(id_append_features, "append_features"); | |
CONST_ID(id_included, "included"); | |
for (i = 0; i < argc; i++) | |
Check_Type(argv[i], T_MODULE); | |
while (argc--) { | |
rb_funcall(argv[argc], id_append_features, 1, module); | |
rb_funcall(argv[argc], id_included, 1, module); | |
} | |
return module; | |
} | |
/* | |
* call-seq: | |
* prepend_features(mod) -> mod | |
* | |
* When this module is prepended in another, Ruby calls | |
* <code>prepend_features</code> in this module, passing it the | |
* receiving module in _mod_. Ruby's default implementation is | |
* to overlay 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#prepend</code>. | |
*/ | |
static VALUE | |
rb_mod_prepend_features(VALUE module, VALUE prepend) | |
{ | |
if (!CLASS_OR_MODULE_P(prepend)) { | |
Check_Type(prepend, T_CLASS); | |
} | |
rb_prepend_module(prepend, module); | |
return module; | |
} | |
/* | |
* call-seq: | |
* prepend(module, ...) -> self | |
* | |
* Invokes <code>Module.prepend_features</code> on each parameter in reverse order. | |
*/ | |
static VALUE | |
rb_mod_prepend(int argc, VALUE *argv, VALUE module) | |
{ | |
int i; | |
ID id_prepend_features, id_prepended; | |
CONST_ID(id_prepend_features, "prepend_features"); | |
CONST_ID(id_prepended, "prepended"); | |
for (i = 0; i < argc; i++) | |
Check_Type(argv[i], T_MODULE); | |
while (argc--) { | |
rb_funcall(argv[argc], id_prepend_features, 1, module); | |
rb_funcall(argv[argc], id_prepended, 1, module); | |
} | |
return module; | |
} | |
static VALUE | |
hidden_identity_hash_new(void) | |
{ | |
VALUE hash = rb_hash_new(); | |
rb_funcall(hash, rb_intern("compare_by_identity"), 0); | |
RBASIC_CLEAR_CLASS(hash); /* hide from ObjectSpace */ | |
return hash; | |
} | |
void | |
rb_using_refinement(NODE *cref, VALUE klass, VALUE module) | |
{ | |
VALUE iclass, c, superclass = klass; | |
Check_Type(klass, T_CLASS); | |
Check_Type(module, T_MODULE); | |
if (NIL_P(cref->nd_refinements)) { | |
RB_OBJ_WRITE(cref, &cref->nd_refinements, hidden_identity_hash_new()); | |
} | |
else { | |
if (cref->flags & NODE_FL_CREF_OMOD_SHARED) { | |
RB_OBJ_WRITE(cref, &cref->nd_refinements, rb_hash_dup(cref->nd_refinements)); | |
cref->flags &= ~NODE_FL_CREF_OMOD_SHARED; | |
} | |
if (!NIL_P(c = rb_hash_lookup(cref->nd_refinements, klass))) { | |
superclass = c; | |
while (c && RB_TYPE_P(c, T_ICLASS)) { | |
if (RBASIC(c)->klass == module) { | |
/* already used refinement */ | |
return; | |
} | |
c = RCLASS_SUPER(c); | |
} | |
} | |
} | |
FL_SET(module, RMODULE_IS_OVERLAID); | |
c = iclass = rb_include_class_new(module, superclass); | |
RCLASS_REFINED_CLASS(c) = klass; | |
RCLASS_M_TBL_WRAPPER(OBJ_WB_UNPROTECT(c)) = | |
RCLASS_M_TBL_WRAPPER(OBJ_WB_UNPROTECT(module)); | |
module = RCLASS_SUPER(module); | |
while (module && module != klass) { | |
FL_SET(module, RMODULE_IS_OVERLAID); | |
c = RCLASS_SET_SUPER(c, rb_include_class_new(module, RCLASS_SUPER(c))); | |
RCLASS_REFINED_CLASS(c) = klass; | |
module = RCLASS_SUPER(module); | |
} | |
rb_hash_aset(cref->nd_refinements, klass, iclass); | |
} | |
static int | |
using_refinement(VALUE klass, VALUE module, VALUE arg) | |
{ | |
NODE *cref = (NODE *) arg; | |
rb_using_refinement(cref, klass, module); | |
return ST_CONTINUE; | |
} | |
static void | |
using_module_recursive(NODE *cref, VALUE klass) | |
{ | |
ID id_refinements; | |
VALUE super, module, refinements; | |
super = RCLASS_SUPER(klass); | |
if (super) { | |
using_module_recursive(cref, super); | |
} | |
switch (BUILTIN_TYPE(klass)) { | |
case T_MODULE: | |
module = klass; | |
break; | |
case T_ICLASS: | |
module = RBASIC(klass)->klass; | |
break; | |
default: | |
rb_raise(rb_eTypeError, "wrong argument type %s (expected Module)", | |
rb_obj_classname(klass)); | |
break; | |
} | |
CONST_ID(id_refinements, "__refinements__"); | |
refinements = rb_attr_get(module, id_refinements); | |
if (NIL_P(refinements)) return; | |
rb_hash_foreach(refinements, using_refinement, (VALUE) cref); | |
} | |
void | |
rb_using_module(NODE *cref, VALUE module) | |
{ | |
Check_Type(module, T_MODULE); | |
using_module_recursive(cref, module); | |
rb_clear_method_cache_by_class(rb_cObject); | |
} | |
VALUE | |
rb_refinement_module_get_refined_class(VALUE module) | |
{ | |
ID id_refined_class; | |
CONST_ID(id_refined_class, "__refined_class__"); | |
return rb_attr_get(module, id_refined_class); | |
} | |
static void | |
add_activated_refinement(VALUE activated_refinements, | |
VALUE klass, VALUE refinement) | |
{ | |
VALUE iclass, c, superclass = klass; | |
if (!NIL_P(c = rb_hash_lookup(activated_refinements, klass))) { | |
superclass = c; | |
while (c && RB_TYPE_P(c, T_ICLASS)) { | |
if (RBASIC(c)->klass == refinement) { | |
/* already used refinement */ | |
return; | |
} | |
c = RCLASS_SUPER(c); | |
} | |
} | |
FL_SET(refinement, RMODULE_IS_OVERLAID); | |
c = iclass = rb_include_class_new(refinement, superclass); | |
RCLASS_REFINED_CLASS(c) = klass; | |
refinement = RCLASS_SUPER(refinement); | |
while (refinement) { | |
FL_SET(refinement, RMODULE_IS_OVERLAID); | |
c = RCLASS_SET_SUPER(c, rb_include_class_new(refinement, RCLASS_SUPER(c))); | |
RCLASS_REFINED_CLASS(c) = klass; | |
refinement = RCLASS_SUPER(refinement); | |
} | |
rb_hash_aset(activated_refinements, klass, iclass); | |
} | |
/* | |
* call-seq: | |
* refine(klass) { block } -> module | |
* | |
* Refine <i>klass</i> in the receiver. | |
* | |
* Returns an overlaid module. | |
*/ | |
static VALUE | |
rb_mod_refine(VALUE module, VALUE klass) | |
{ | |
VALUE refinement; | |
ID id_refinements, id_activated_refinements, | |
id_refined_class, id_defined_at; | |
VALUE refinements, activated_refinements; | |
rb_thread_t *th = GET_THREAD(); | |
rb_block_t *block = rb_vm_control_frame_block_ptr(th->cfp); | |
if (!block) { | |
rb_raise(rb_eArgError, "no block given"); | |
} | |
if (block->proc) { | |
rb_raise(rb_eArgError, | |
"can't pass a Proc as a block to Module#refine"); | |
} | |
Check_Type(klass, T_CLASS); | |
CONST_ID(id_refinements, "__refinements__"); | |
refinements = rb_attr_get(module, id_refinements); | |
if (NIL_P(refinements)) { | |
refinements = hidden_identity_hash_new(); | |
rb_ivar_set(module, id_refinements, refinements); | |
} | |
CONST_ID(id_activated_refinements, "__activated_refinements__"); | |
activated_refinements = rb_attr_get(module, id_activated_refinements); | |
if (NIL_P(activated_refinements)) { | |
activated_refinements = hidden_identity_hash_new(); | |
rb_ivar_set(module, id_activated_refinements, | |
activated_refinements); | |
} | |
refinement = rb_hash_lookup(refinements, klass); | |
if (NIL_P(refinement)) { | |
refinement = rb_module_new(); | |
RCLASS_SET_SUPER(refinement, klass); | |
FL_SET(refinement, RMODULE_IS_REFINEMENT); | |
CONST_ID(id_refined_class, "__refined_class__"); | |
rb_ivar_set(refinement, id_refined_class, klass); | |
CONST_ID(id_defined_at, "__defined_at__"); | |
rb_ivar_set(refinement, id_defined_at, module); | |
rb_hash_aset(refinements, klass, refinement); | |
add_activated_refinement(activated_refinements, klass, refinement); | |
} | |
rb_yield_refine_block(refinement, activated_refinements); | |
return refinement; | |
} | |
/* | |
* call-seq: | |
* using(module) -> self | |
* | |
* Import class refinements from <i>module</i> into the current class or | |
* module definition. | |
*/ | |
static VALUE | |
mod_using(VALUE self, VALUE module) | |
{ | |
NODE *cref = rb_vm_cref(); | |
rb_control_frame_t *prev_cfp = previous_frame(GET_THREAD()); | |
if (prev_frame_func()) { | |
rb_raise(rb_eRuntimeError, | |
"Module#using is not permitted in methods"); | |
} | |
if (prev_cfp && prev_cfp->self != self) { | |
rb_raise(rb_eRuntimeError, "Module#using is not called on self"); | |
} | |
rb_using_module(cref, module); | |
return self; | |
} | |
void | |
rb_obj_call_init(VALUE obj, int argc, const VALUE *argv) | |
{ | |
PASS_PASSED_BLOCK(); | |
rb_funcall2(obj, idInitialize, argc, argv); | |
} | |
void | |
rb_extend_object(VALUE obj, VALUE 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(VALUE mod, VALUE 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(int argc, VALUE *argv, VALUE obj) | |
{ | |
int i; | |
ID id_extend_object, id_extended; | |
CONST_ID(id_extend_object, "extend_object"); | |
CONST_ID(id_extended, "extended"); | |
rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS); | |
for (i = 0; i < argc; i++) | |
Check_Type(argv[i], T_MODULE); | |
while (argc--) { | |
rb_funcall(argv[argc], id_extend_object, 1, obj); | |
rb_funcall(argv[argc], id_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(int argc, VALUE *argv, VALUE self) | |
{ | |
rb_thread_t *th = GET_THREAD(); | |
if (th->top_wrapper) { | |
rb_warning("main.include in the wrapped load is effective only in wrapper module"); | |
return rb_mod_include(argc, argv, th->top_wrapper); | |
} | |
return rb_mod_include(argc, argv, rb_cObject); | |
} | |
/* | |
* call-seq: | |
* using(module) -> self | |
* | |
* Import class refinements from <i>module</i> into the scope where | |
* <code>using</code> is called. | |
*/ | |
static VALUE | |
top_using(VALUE self, VALUE module) | |
{ | |
NODE *cref = rb_vm_cref(); | |
rb_control_frame_t *prev_cfp = previous_frame(GET_THREAD()); | |
if (cref->nd_next || (prev_cfp && prev_cfp->me)) { | |
rb_raise(rb_eRuntimeError, | |
"main.using is permitted only at toplevel"); | |
} | |
rb_using_module(cref, module); | |
return self; | |
} | |
static VALUE * | |
errinfo_place(rb_thread_t *th) | |
{ | |
rb_control_frame_t *cfp = th->cfp; | |
rb_control_frame_t *end_cfp = RUBY_VM_END_CONTROL_FRAME(th); | |
while (RUBY_VM_VALID_CONTROL_FRAME_P(cfp, end_cfp)) { | |
if (RUBY_VM_NORMAL_ISEQ_P(cfp->iseq)) { | |
if (cfp->iseq->type == ISEQ_TYPE_RESCUE) { | |
return &cfp->ep[-2]; | |
} | |
else if (cfp->iseq->type == ISEQ_TYPE_ENSURE && | |
!RB_TYPE_P(cfp->ep[-2], T_NODE) && | |
!FIXNUM_P(cfp->ep[-2])) { | |
return &cfp->ep[-2]; | |
} | |
} | |
cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); | |
} | |
return 0; | |
} | |
static VALUE | |
get_thread_errinfo(rb_thread_t *th) | |
{ | |
VALUE *ptr = errinfo_place(th); | |
if (ptr) { | |
return *ptr; | |
} | |
else { | |
return th->errinfo; | |
} | |
} | |
static VALUE | |
get_errinfo(void) | |
{ | |
return get_thread_errinfo(GET_THREAD()); | |
} | |
static VALUE | |
errinfo_getter(ID id) | |
{ | |
return get_errinfo(); | |
} | |
#if 0 | |
static void | |
errinfo_setter(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 $!"); | |
} | |
else { | |
VALUE *ptr = errinfo_place(GET_THREAD()); | |
if (ptr) { | |
*ptr = val; | |
} | |
else { | |
rb_raise(rb_eRuntimeError, "errinfo_setter: not in rescue clause."); | |
} | |
} | |
} | |
#endif | |
VALUE | |
rb_errinfo(void) | |
{ | |
rb_thread_t *th = GET_THREAD(); | |
return th->errinfo; | |
} | |
void | |
rb_set_errinfo(VALUE err) | |
{ | |
if (!NIL_P(err) && !rb_obj_is_kind_of(err, rb_eException)) { | |
rb_raise(rb_eTypeError, "assigning non-exception to $!"); | |
} | |
GET_THREAD()->errinfo = err; | |
} | |
VALUE | |
rb_rubylevel_errinfo(void) | |
{ | |
return get_errinfo(); | |
} | |
static VALUE | |
errat_getter(ID id) | |
{ | |
VALUE err = get_errinfo(); | |
if (!NIL_P(err)) { | |
return get_backtrace(err); | |
} | |
else { | |
return Qnil; | |
} | |
} | |
static void | |
errat_setter(VALUE val, ID id, VALUE *var) | |
{ | |
VALUE err = get_errinfo(); | |
if (NIL_P(err)) { | |
rb_raise(rb_eArgError, "$! not set"); | |
} | |
set_backtrace(err, val); | |
} | |
/* | |
* call-seq: | |
* __method__ -> symbol | |
* | |
* Returns the name at the definition of the current method as a | |
* Symbol. | |
* If called outside of a method, it returns <code>nil</code>. | |
* | |
*/ | |
static VALUE | |
rb_f_method_name(void) | |
{ | |
ID fname = prev_frame_func(); /* need *method* ID */ | |
if (fname) { | |
return ID2SYM(fname); | |
} | |
else { | |
return Qnil; | |
} | |
} | |
/* | |
* call-seq: | |
* __callee__ -> symbol | |
* | |
* Returns the called name of the current method as a Symbol. | |
* If called outside of a method, it returns <code>nil</code>. | |
* | |
*/ | |
static VALUE | |
rb_f_callee_name(void) | |
{ | |
ID fname = prev_frame_callee(); /* need *callee* ID */ | |
if (fname) { | |
return ID2SYM(fname); | |
} | |
else { | |
return Qnil; | |
} | |
} | |
/* | |
* call-seq: | |
* __dir__ -> string | |
* | |
* Returns the canonicalized absolute path of the directory of the file from | |
* which this method is called. It means symlinks in the path is resolved. | |
* If <code>__FILE__</code> is <code>nil</code>, it returns <code>nil</code>. | |
* The return value equals to <code>File.dirname(File.realpath(__FILE__))</code>. | |
* | |
*/ | |
static VALUE | |
f_current_dirname(void) | |
{ | |
VALUE base = rb_current_realfilepath(); | |
if (NIL_P(base)) { | |
return Qnil; | |
} | |
base = rb_file_dirname(base); | |
return base; | |
} | |
void | |
Init_eval(void) | |
{ | |
rb_define_virtual_variable("$@", errat_getter, errat_setter); | |
rb_define_virtual_variable("$!", errinfo_getter, 0); | |
rb_define_global_function("raise", rb_f_raise, -1); | |
rb_define_global_function("fail", rb_f_raise, -1); | |
rb_define_global_function("global_variables", rb_f_global_variables, 0); /* in variable.c */ | |
rb_define_global_function("__method__", rb_f_method_name, 0); | |
rb_define_global_function("__callee__", rb_f_callee_name, 0); | |
rb_define_global_function("__dir__", f_current_dirname, 0); | |
rb_define_method(rb_cModule, "include", rb_mod_include, -1); | |
rb_define_method(rb_cModule, "prepend", rb_mod_prepend, -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, "prepend_features", rb_mod_prepend_features, 1); | |
rb_define_private_method(rb_cModule, "refine", rb_mod_refine, 1); | |
rb_define_private_method(rb_cModule, "using", mod_using, 1); | |
rb_undef_method(rb_cClass, "refine"); | |
rb_undef_method(rb_cClass, "module_function"); | |
Init_vm_eval(); | |
Init_eval_method(); | |
rb_define_singleton_method(rb_cModule, "nesting", rb_mod_nesting, 0); | |
rb_define_singleton_method(rb_cModule, "constants", rb_mod_s_constants, -1); | |
rb_define_private_method(rb_singleton_class(rb_vm_top_self()), | |
"include", top_include, -1); | |
rb_define_private_method(rb_singleton_class(rb_vm_top_self()), | |
"using", top_using, 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_vm_register_special_exception(ruby_error_reenter, rb_eFatal, "exception reentered"); | |
} |